Числа с плавающей точкой
Содержание
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 × 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 × 2-51
= 1.229999999999999982236431605997495353221893310546875
А число 2.34 равно:
5269211564023480 × 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, в которой алгоритм округления был усовершенствован и стал работать примерно так:
- Округлить до 15 цифр.
- Преобразовать полученный текст обратно в Binary-64.
- Если значение 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 × 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