RussianLDP Рейтинг@Mail.ru
WebMoney: 
WMZ Z294115950220 
WMR R409981405661 
WME E134003968233 
Visa 
4274 3200 2453 6495 

Small. Fast. Reliable.
Choose any three.

1. Как SQLite хранит числа

SQLite хранит целочисленные значения в 64-битном формате twos-complement¹. Это дает диапазон хранения от -9223372036854775808 до +9223372036854775807, включительно. Целые числа в этом диапазоне точны.

Так называемый "REAL" или значения с плавающей точкой сохранены в формате IEEE 754 Binary-64 ¹. Это дает диапазон положительных значений примерно от 1.7976931348623157e+308 до 4.9406564584124654e-324 с эквивалентным диапазоном отрицательных величин. binary64 может также быть 0.0 (и -0.0), положительной и отрицательной бесконечностью и "NaN" или "Не-число". Значения с плавающей точкой приблизительны.

Обратите пристальное внимание на последнее предложение в предыдущем параграфе:

Значения с плавающей точкой приблизительны.

Если вам нужен точный ответ, вы не должны использовать значения binary64 с плавающей запятой в SQLite или ни в каком другом продукте. Это не ограничение SQLite. Это математическое ограничение, врожденное от дизайна чисел с плавающей запятой.


¹ Исключение: Расширение R-Tree хранит информацию как 32-битные значения с плавающей точкой или целочисленные значения.

1.1. Точность с плавающей запятой

SQLite обещает сохранить 15 старших значащих цифр значения с плавающей точкой. Однако, это не делает гарантий точности вычислений на значениях с плавающей точкой, поскольку никакие такие гарантии невозможны. Выполнение математики на значениях с плавающей запятой вводит ошибку. Например, рассмотрите то, что происходит, при попытке вычесть два числа с плавающей запятой подобной величины:

1152693165.1106291898
-1152693165.1106280772

0.0000011126

Результатом, показанным выше (0.0000011126), является правильный ответ. Но если вы делаете это вычисление, используя binary64 с плавающей запятой, ответ, который вы получаете, 0.00000095367431640625, ошибка приблизительно 14%. Если вы делаете много подобных вычислений как часть вашей программы, ошибки складываются так, что ваш конечный результат мог бы быть абсолютно бессмысленным.

Ошибка возникает, потому что только первые 15 значительных цифр каждого числа сохранены точно, и первое различие между этими двумя вычитаемыми числами находится в 16-й цифре.

1.2. Числа с плавающей точкой

Формат с плавающей запятой binary64 использует 64 бита на число. Следовательно, есть 1.845e+19 различных возможных значения с плавающей точкой. С другой стороны, есть бесконечно много вещественных чисел в диапазоне от 1.7977e+308 до 4.9407e-324. Значит binary64 не может представлять все возможные вещественные числа в этом диапазоне. Приближения требуются.

Значение IEEE 754 с плавающей запятой является целым числом, умноженным на тсепень 2:

M × 2E

M это мантисса, E экспонента. M и E это integer.

Для Binary64 M это 53-bit integer, E это 11-bit integer, которое смещено так, чтобы представлять диапазон значений между -1074 и +972, включительно.

Обычное описание IEEE 754 более сложно, и важно понять добавленную сложность, если вы действительно хотите оценить детали, достоинства и ограничения IEEE 754. Однако, описание целого числа, показанное здесь, в то время как не совсем точно, легче понять, и этого достаточно в целях этой статьи.

1.2.1. Непредставляемые числа

Не каждое десятичное число меньше чем с 16 значительными цифрами может быть представлено точно как binary64-число. На самом деле большинство десятичных чисел с цифрами направо от десятичной точки испытывает недостаток в точном binary64-эквиваленте. Например, если у вас есть колонка базы данных, которая предназначается, чтобы хранить цену на пункт в долларах и центах, единственная покупательная сила центов, которая может быть точно представлена, 0.00, 0.25, 0.50, и 0.75. Любые другие числа направо от десятичной точки приводят к приближению. Если вы предоставите значение "price" 47.49, то число будет представлено в binary64 как:

6683623321994527 × 2-47

Это работает так:

47.49000000000000198951966012828052043914794921875

Это число очень близко к 47.49, но это не точно. Это немного слишком большое. Если мы уменьшаем M на один к 6683623321994526, чтобы у нас было следующее меньшее возможное значение binary64:

47.4899999999999948840923025272786617279052734375

Это второе число слишком маленькое. Первое число ближе к требуемому значению 47.49. Но это не точно. Большинство десятичных значений работают в IEEE 754 примерно так. Помните ключевое мнение, которое мы высказали выше:

Значения с плавающей точкой приблизительны.

Если вы не помните ничто иное о значениях с плавающей запятой, пожалуйста, не забывайте эту ключевую идею.

1.2.2. Это достаточно близко?

Точность, обеспеченная IEEE 754 Binary64, достаточна для большинства вычислений. Например, если "47.49" представляет цену, и инфляция достигает 2% в год, то цена повышается приблизительно на 0.0000000301 в секунду. Ошибка в зарегистрированном значении 47.49 представляет ценность приблизительно 66 наносекунд инфляции. Таким образом, если 47.49 точна при вводе, эффекты инфляции заставят истинное значение точно равняться стоимости (47.4900000000000019895196601282805204391479492187) с поправкой меньше, чем в одну десятимиллионную секунды. Конечно, тот уровень точности достаточен в большинстве ситуаций.

2. Расширения для контакта с числами с плавающей точкой

2.1. Расширение ieee754.c

Расширение ieee754 преобразовывает число с плавающей точкой между представлением binary64 и форматом M×2E. Другими словами, в выражении:

F = M × 2E

расширение ieee754 преобразовывает между F и (M, E) и назад.

Расширение ieee754 не часть объединения, но это включено по умолчанию в CLI. Если вы захотите включить расширение ieee754 в свое приложение, необходимо будет собрать и загрузить его отдельно.

2.1.1. Функция ieee754()

Функция ieee754(F) SQL берет единственный аргумент с плавающей запятой в качестве своего входа и возвращает последовательность, которая похожа на это:

'ieee754(M,E)'

За исключением того, что M и E заменяются мантиссой и экспонентой числа с плавающей точкой. Например:

sqlite> .mode box
sqlite> SELECT ieee754(47.49) AS x;
+-------------------------------+
|                x              |
+-------------------------------+
|  ieee754(6683623321994527,-47)|
+-------------------------------+

С другой стороны, версия с 2 аргументами ieee754() берет M и E, оценивает и преобразовывает их в соответствующее значение F:

sqlite> select ieee754(6683623321994527,-47) as x;
+-------+
|   x   |
+-------+
| 47.49 |
+-------+

2.1.2. Функции ieee754_mantissa() и ieee754_exponent()

Текстовый вывод формы с одним аргументом ieee754() больше для удобства чтения, но это неудобно использовать в качестве части большего выражения. Следовательно, ieee754_mantissa() и ieee754_exponent() были добавлены, чтобы возвратить M и E, соответствующие их значениям отдельного аргумента F. Например:

sqlite> .mode box
sqlite> SELECT ieee754_mantissa(47.49) AS M, ieee754_exponent(47.49) AS E;
+------------------+-----+
|        M         |  E  |
+------------------+-----+
| 6683623321994527 | -47 |
+------------------+-----+

2.1.3. Функции ieee754_from_blob() и ieee754_to_blob()

SQL-функция ieee754_to_blob(F) преобразовывает число с плавающей точкой F в 8-байтовый BLOB, который является обратным порядком байтов binary64-кодирования того числа. Функция ieee754_from_blob(B) идет другим путем, преобразовывая 8-байтовый blob в значение с плавающей запятой, которое представляет кодирование binary64.

Так, например, если вы читаете Wikipedia, что кодирование для минимального положительного значения binary64 это 0x0000000000000001, тогда можно найти соответствующее значение с плавающей точкой:

sqlite> .mode box
sqlite> SELECT ieee754_from_blob(x'0000000000000001') AS F;
+-----------------------+
|            F          |
+-----------------------+
| 4.94065645841247e-324 |
+-----------------------+

Или пойдите другим путем:

sqlite> .mode box
sqlite> SELECT quote(ieee754_to_blob(4.94065645841247e-324)) AS binary64;
+--------------------+
|     binary64       |
+--------------------+
|X'0000000000000001' |
+--------------------+

2.2. Расширение decimal.c

Расширение decimal обеспечивает десятичную систему исчисления произвольной точности на числах, сохраненных как текстовые строки. Поскольку числа сохранены к произвольной точности и как текст, никакие приближения не необходимы. Вычисления могут быть сделаны точно.

Десятичное расширение (в настоящее время) не часть объединения SQLite. Однако, это включено в CLI.

Есть три доступные математических функции:

  • decimal_add(A,B)
  • decimal_sub(A,B)
  • decimal_mul(A,B)
  • decimal_pow2(N)

Первые три функции соответственно добавляют, вычитают и умножают свои аргументы и возвращают новую текстовую строку, которая является десятичным представлением результата. Аргумент интерпретируется как текст. Нет никакого оператора деления, потому что десятичное деление часто не производит конечный десятичный результат.

Функция decimal_pow2(N) вернет 2.0 в степени N, где N это integer от -20000 до +20000.

Используйте decimal_cmp(A,B), чтобы сравнить два десятичных значения. Результат будет отрицательным, нолем или положительным, если A будет меньше, равно или больше B, соответственно.

Функция decimal_sum(X) это совокупность, как встроенная функция sum(), за исключением того, что decimal_sum() вычисляет свой результат с произвольной точностью и поэтому точна.

Десятичное расширение обеспечивает последовательность сопоставления "decimal", которая сравнивает десятичные текстовые строки в числовом порядке.

decimal(X) и decimal_exp(X) производят десятичное представление для входа X. decimal_exp(X) возвращает результат в экспоненциальном представлении (с "e+NN" в конце) и decimal(X) вернет чистое десятичное число (без "e+NN"). Если вход X является значением с плавающей точкой, он расширен до его точного десятичного эквивалента. Например:

sqlite> .mode qbox
sqlite> select decimal(47.49);
+------------------------------------------------------+
|                     decimal(47.49)                   |
+------------------------------------------------------+
|  '47.49000000000000198951966012828052043914794921875'|
+------------------------------------------------------+