Spec-Zone.ru › SQLite

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

Содержание
1. Как SQLite хранит числа
1.1. Точность чисел с плавающей точкой
1.2. Числа с плавающей точкой
1.2.1. Непредставимые числа
1.2.2. Достаточно ли это близко?
1.3. Округление чисел с плавающей точкой для отображения
1.3.1. Выбор количества отображаемых цифр числа с плавающей точкой
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 бита¹. Это обеспечивает диапазон хранения от -9223372036854775808 до +9223372036854775807 включительно. Целые числа в этом диапазоне хранятся точно.

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

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

 Floating point values are approximate. 

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

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

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

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

1152693165.1106291898
-1152693165.1106280772
0.0000011126

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

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

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

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

Значение с плавающей точкой IEEE 754 представляет собой целое число, умноженное на степень двойки:

M &times 2E

Значение M — это «мантисса», а E — «показатель степени». И M, и E являются целыми числами.

В формате Binary-64 M — это 53-разрядное целое число, а E — 11-разрядное целое число со смещением, которое позволяет представлять значения от -1074 до +972 включительно.

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

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

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

6683623321994527 × 2-47

Что соответствует значению:

47.49000000000000198951966012828052043914794921875

Это число очень близко к 47.49, но не равно ему точно. Оно немного больше. Если уменьшить M на единицу, до 6683623321994526, чтобы получить следующее меньшее возможное значение Binary-64, мы получим:

47.4899999999999948840923025272786617279052734375

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

 Floating point values are approximate. 

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

1.2.2. Достаточно ли это близко?

Точности, обеспечиваемой IEEE 754 Binary-64, достаточно для большинства вычислений. Например, если «47.49» — это цена, а инфляция составляет 2% в год, то цена увеличивается примерно на 0.0000000301 доллара в секунду. Погрешность записанного значения 47.49 соответствует примерно 66 наносекундам инфляции. Поэтому, если цена 47.49 была точной в момент ввода, влияние инфляции приведёт к тому, что истинное значение станет в точности равно фактически сохранённому значению (47.4900000000000019895196601282805204391479492187) менее чем за одну десятитысячную долю секунды. Разве такой точности не достаточно для большинства задач?

1.3. Округление чисел с плавающей точкой для отображения

При преобразовании числа Binary-64 в текст для отображения (например, при вызове sqlite3_column_text() или sqlite3_value_text() либо при использовании оператора CAST в SQL) значение обычно округляется до разумного количества значащих цифр. Редко кому нужно видеть все 49 значащих цифр числа 6683623321994527×pow(2,-47). Обычно лучше отображать первые четыре цифры — в данном случае «47.49».

Округление имеет и причины, связанные с производительностью. Число 6683623321994527×pow(2,-47) можно сравнительно быстро преобразовать в «47.49», а формирование полного разложения из 49 цифр требует гораздо больше вычислений.

В какой момент следует выполнять округление? Округление до 17 и более значащих цифр даёт текст, который при обратном преобразовании в Binary-64 возвращает точно то же значение, с которого мы начали. Округление до 15 или менее значащих цифр часто даёт такой же точный результат обратного преобразования, но не всегда. С другой стороны, округление до 15 цифр обычно даёт результаты, которые приятнее читать, тогда как округление до 17 цифр может приводить к длинным числам с множеством «0» или «9» посередине.

До SQLite 3.52.0 (2026-03-06), если попытаться округлить 6683623321994527×pow(2,-47) до 17 цифр, SQLite рассмотрит первые 18 цифр («47.4900000000000019»), увидит, что последняя цифра — 5 или больше, и округлит число вверх до «47.490000000000002». Поэтому в предыдущих версиях SQLite округление выполнялось до 15 цифр. SQLite проверял 16-ю цифру, видел, что она равна нулю, и просто отбрасывал её, затем удалял конечные нули, в результате чего оставалось «47.49» — именно то, что нужно большинству пользователей. Кроме того, при преобразовании текста «47.49» обратно в Binary-64 получается 6683623321994527×pow(2,-47), поэтому значение не меняется при циклическом преобразовании Binary-64→текст→Binary-64. Всё хорошо.

Но этот способ работает не во всех случаях. Рассмотрим, например, произведение 1.23 и 2.34. Число 1.23 равно:

2769713770832855 &times 2-51
= 1.229999999999999982236431605997495353221893310546875

А число 2.34 равно:

5269211564023480 &times 2-51
= 2.339999999999999857891452847979962825775146484375

Если точно перемножить эти два значения, получится:

2.878199999999999783639736961049495926597557229698714817531408904915934954260592348873615264892578125

Это число невозможно представить в формате Binary-64. Оборудование для вычислений с плавающей точкой в вашем процессоре усекает его до:

6481130223748880 × 2-51
= 2.87819999999999964757080306299030780792236328125

Если округлить последнее значение до 15 значащих цифр, получится 2.8782 — именно такой ответ ожидает большинство пользователей. Если округлить до 17 цифр, получится 2.8781999999999997. Однако 2.8782 и 2.8781999999999997 — это разные числа в формате Binary-64.

2.8782 → 6481130223748881 × 2-51
2.8781999999999997 → 6481130223748880 × 2-51

Мантиссы различаются на единицу. Поэтому при обратном преобразовании текста «2.8782» в Binary-64 не получится исходное произведение 1.23×2.34. Результат будет очень близким, но не точным. А при обратном преобразовании текста «2.8781999999999997» в Binary-64 получится исходное значение. Поэтому в этом смысле 2.8781999999999997 — более точный ответ.

1.3.1. Выбор количества отображаемых цифр числа с плавающей точкой

Таким образом, приходится выбирать. Вы (A) округляете до 15 цифр и получаете ответы, более близкие к ожидаемым людьми, или (B) округляете до 17 цифр и получаете ответы, в которых после десятичной точки иногда много «0» или «9», зато они при обратном преобразовании возвращаются к исходному значению Binary-64?

В SQLite 3.51.2 и более ранних версиях был доступен только вариант (A). Начиная с SQLite 3.52.0 (2026-03-06) доступен вариант (B), установленный по умолчанию, хотя приложения могут переключиться обратно на вариант (A). В коде C выбрать вариант (A) можно, выполнив примерно такой код:

sqlite3_db_config(db, SQLITE_DBCONFIG_FP_DIGITS, 15, 0);

Или в CLI можно выполнить:

.dbconfig fp_digits 15

Это изменение появилось в версии 3.52.0, в которой алгоритм округления был усовершенствован и стал работать примерно так:

  1. Округлить до 15 цифр.
  2. Преобразовать полученный текст обратно в Binary-64.
  3. Если значение Binary-64, полученное на предыдущем шаге, отличается, то:
    • Повторить шаг (a), выполнив округление до 17 цифр.

Обычно округления до 15 цифр достаточно. Шаг (c) выполняется редко. Текстовые представления с множеством «0» и «9» появляются только в необычных случаях, когда они имеют значение. Это усовершенствование было внесено в версии 3.52.0 потому, что процедуры преобразования Binary-64→текст→Binary-64 были переписаны и стали использовать целочисленную арифметику с разрядностью 64 бита вместо вычислений с плавающей точкой; благодаря этому шаг (b) приведённого выше алгоритма стал приемлемым с точки зрения вычислительных затрат. Преобразование Binary-64→текст в версии 3.52.0 всё равно быстрее, чем раньше, даже с добавлением шагов (b) и (c). Если вы не хотите незначительных дополнительных затрат на шаги (b) и (c), установите для SQLITE_DBCONFIG_FP_DIGITS значение 15.

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

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

Расширение ieee754 преобразует число с плавающей точкой между его представлением Binary-64 и форматом 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 в 8-байтовый BLOB, представляющий собой кодировку этого числа Binary-64 в порядке от старшего байта к младшему. Функция ieee754_from_blob(B) выполняет обратное преобразование: преобразует 8-байтовый BLOB в значение с плавающей точкой, представленное кодировкой Binary-64.

Например, если вы прочитали в Википедии, что кодировка минимального положительного значения Binary-64 — это 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 binary-64;
┌─────────────────────┐
│      binary-64      │
├─────────────────────┤
│ 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) вычисляет точное десятичное представление 2 в степени N. 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 предоставляет последовательность сортировки «decimal», которая сравнивает десятичные текстовые строки в числовом порядке.

Последнее обновление этой страницы: 2026-04-09 19:12:31Z

SQLite is in the Public Domain.
https://sqlite.org/floatingpoint.html

Spec-Zone.ru

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