Spec-Zone.ru › SQLite

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

Содержание
1. Как SQLite хранит числа
1.1. Точность чисел с плавающей точкой
1.2. Числа с плавающей точкой
1.2.1. Непредставимые числа
1.2.2. Достаточно ли близко?
2. Расширения для работы с числами с плавающей точкой
2.1. Расширение ieee754.c
2.1.1. Функция ieee754()
2.1.2. Функции ieee754_mantissa() и ieee754_exponent()
2.1.3. Функции ieee754_from_blob() и ieee754_to_blob()
2.2. Расширение decimal.c
2.2.1. Функции decimal_add(A,B), decimal_sub(A,B) и decimal_mul(A,B)
2.2.2. Функция decimal_pow2(N)
2.2.3. Функции decimal(X) и decimal_exp(X)
2.2.4. Функция decimal_cmp(X)
2.2.5. Функция агрегирования decimal_sum(X)
2.2.6. Последовательность сортировки decimal

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

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

Так называемые значения "REAL" или с плавающей точкой хранятся в формате IEEE 754 двойной точности&sup1. Это обеспечивает диапазон положительных значений примерно от 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 &times 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 &times 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API