Числа с плавающей точкой
Содержание
1. Как SQLite хранит числа
SQLite хранит целые значения в формате 64-битного дополнения до двух¹. Это обеспечивает диапазон хранения от -9223372036854775808 до +9223372036854775807 включительно. Целые числа в этом диапазоне являются точными.
Так называемые значения "REAL" или с плавающей точкой хранятся в формате IEEE 754 двойной точности¹. Это обеспечивает диапазон положительных значений примерно от 1.7976931348623157e+308 до 4.9406564584124654e-324 и эквивалентный диапазон отрицательных значений. Число с плавающей точкой типа binary64 также может быть 0.0 (и -0.0), положительной или отрицательной бесконечностью и "NaN" или "Не число". Значения с плавающей точкой являются приближёнными.
Обратите пристальное внимание на последнее предложение в предыдущем абзаце:
Floating point values are approximate.
Если вам нужна точная ответ, вы не должны использовать значения с плавающей точкой 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 – это целое число, умноженное на степень двойки:
M × 2E
Значение M – это "мантисса", а E – "экспонента". И M, и E – целые числа.
Для Binary64 M – 53-битное целое число, а E – 11-битное целое число, смещённое так, что оно представляет диапазон значений от -1074 до +972 включительно.
(Примечание: Обычное описание IEEE 754 более сложное, и важно понять дополнительную сложность, если вы действительно хотите оценить детали, достоинства и ограничения IEEE 754. Однако целое числовое описание, хотя и не совсем верное, легче понять и достаточно для целей этой статьи.)
1.2.1. Непредставимые числа
Не каждое десятичное число с менее чем 16 значащими цифрами может быть точно представлено как число binary64. На самом деле, большинство десятичных чисел с цифрами справа от десятичной точки не имеют точного эквивалента binary64. Например, если у вас есть столбец базы данных, предназначенный для хранения цены товара в долларах и центах, то единственными центами, которые могут быть точно представлены, являются 0.00, 0.25, 0.50 и 0.75. Любые другие числа справа от десятичной точки приводят к приближению. Если вы предоставите значение "цены" 47,49, то это число будет представлено в binary64 как:
6683623321994527 × 2-47
Что равно:
47.49000000000000198951966012828052043914794921875
Это число очень близко к 47,49, но не точно. Оно немного больше. Если мы уменьшим M на единицу до 6683623321994526, так что у нас есть следующее меньшее возможное значение binary64, мы получим:
47.4899999999999948840923025272786617279052734375
Это второе число слишком мало. Первое число ближе к желаемому значению 47,49, поэтому оно и используется. Но оно не точно. Большинство десятичных значений работают таким образом в IEEE 754. Помните ключевую точку, которую мы указали выше:
Floating point values are approximate.
Если вы ничего не помните о числах с плавающей точкой, пожалуйста, не забывайте об этой ключевой идее.
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()
SQL-функция ieee754(F) принимает единственный аргумент с плавающей точкой в качестве входных данных и возвращает строку, которая выглядит так:
'ieee754(M,E)'
За исключением того, что M и E заменяются мантиссой и экспонентой числа с плавающей точкой. Например:
sqlite> .mode box sqlite> SELECT ieee754(47.49) AS x; ┌───────────────────────────────┐ │ x │ ├───────────────────────────────┤ │ ieee754(6683623321994527,-47) │ └───────────────────────────────┘
В обратном направлении двухаргументная версия 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 в BLOB размером 8 байт, который представляет собой big-endian двоичное кодирование binary64 этого числа. Функция ieee754_from_blob(B) делает обратное, преобразуя 8-байтовый блок в число с плавающей точкой, которое представляет двоичное кодирование binary64.
Например, если вы прочтёте на Википедии, что кодирование для минимального положительного значения 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 предоставляет арифметику с произвольной точностью для десятичных чисел, хранящихся в виде текстовых строк. Поскольку числа хранятся с произвольной точностью и в виде текста, приближения не требуются. Вычисления могут выполняться точно.
Расширение decimal (на данный момент) не является частью склейки SQLite. Однако оно включено в CLI. Исходный код этого расширения можно найти по адресу ext/misc/decimal.c.
Расширение decimal поддерживает следующие SQL-функции и последовательности сортировки:
2.2.1. Функции decimal_add(A,B), decimal_sub(A,B) и decimal_mul(A,B)
Эти функции вычисляют сумму, разность и произведение двух входных данных A и B, соответственно, и возвращают результат в виде текста. Входные данные могут быть либо текстовыми десятичными значениями, либо числовыми значениями. Числовые входные данные преобразуются в десятичный текст перед выполнением вычислений.
Функции «decimal_div(A,B)» нет, потому что деление не всегда имеет конечный десятичный результат.
2.2.2. Функция decimal_pow2(N)
Функция decimal_pow2(N) вычисляет точное десятичное представление N-й степени числа 2. N должно быть целым числом от -20000 до +20000.
Эта функция может быть медленной и использовать много памяти для больших значений N.
2.2.3. Функции decimal(X) и decimal_exp(X)
Функции decimal(X) и decimal_exp(X) генерируют десятичное представление входного значения 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' │ └──────────────────────────────────────────────────────┘
2.2.4. Функция decimal_cmp(X)
Функция decimal_cmp(A,B) сравнивает два десятичных значения A и B. Результат будет отрицательным, нулевым или положительным, если A меньше, равно или больше B, соответственно.
2.2.5. Функция агрегирования decimal_sum(X)
Функция decimal_sum(X) является функцией агрегирования, как встроенная функция sum(), за исключением того, что decimal_sum() вычисляет свой результат с произвольной точностью и, следовательно, является точной.
2.2.6. Десятичная последовательность сортировки
Расширение decimal предоставляет последовательность сортировки «decimal», которая сравнивает десятичные текстовые строки в числовом порядке.
Эта страница была последним обновлена 25 июля 2024 г. 10:34:20 UTC
SQLite is in the Public Domain.
https://sqlite.org/floatingpoint.html