Программирование SQLDDL и типы данныхРазработчик серверной части

Сравните точный и приближённый числовые типы SQL: почему денежную сумму обычно хранят не в типе с плавающей...

Сравните точный и приближённый числовые типы SQL: почему денежную сумму обычно хранят не в типе с плавающей точкой?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Денежные суммы обычно хранят в DECIMAL или NUMERIC, потому что это точные десятичные типы: заданные цифры и масштаб сохраняются предсказуемо. Типы с плавающей точкой, такие как REAL и DOUBLE PRECISION, используют приближённое представление, поэтому некоторые десятичные дроби хранятся с небольшой погрешностью.

Эта погрешность может проявиться при сложении большого количества значений, сравнении чисел на равенство и расчёте итогов. Однако точный тип не отменяет необходимости выбрать достаточные точность и масштаб: при неподходящих параметрах возможны округление или переполнение.

Исторический контекст

SQL должен был представлять разные виды чисел: целые значения, точные десятичные величины и результаты научных или инженерных вычислений с широким диапазоном. Поэтому в стандарте появились отдельные категории точных числовых типов и приближённых числовых типов.

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

Постановка проблемы

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

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

Противоположная ошибка — считать DECIMAL безусловно безопасным. Если столбец имеет слишком малый масштаб, дробная часть будет округляться по правилам конкретной СУБД или настроек режима, а если не хватает целой части, операция может завершиться ошибкой переполнения.

Подробное решение

DECIMAL(p, s) и NUMERIC(p, s) описывают число с общей точностью p и количеством знаков после десятичного разделителя s. Например, DECIMAL(12, 2) обычно допускает до 10 цифр в целой части и 2 цифры после разделителя; точные детали проверки границ зависят от СУБД, но смысл параметров одинаков.

Типы REAL, FLOAT и DOUBLE PRECISION являются приближёнными. Они обычно используют формат с плавающей точкой и ограниченное количество значащих битов, поэтому арифметика может давать результат, близкий к математическому, но не идентичный ему.

Минимальный пример:

CREATE TABLE invoices ( invoice_id INTEGER PRIMARY KEY, amount DECIMAL(12, 2) NOT NULL, measurement DOUBLE PRECISION ); INSERT INTO invoices VALUES (1, 10.10, 10.10);

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

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

Альтернатива — хранить минимальные денежные единицы, например копейки, в INTEGER или BIGINT. Это упрощает точную арифметику, но требует самостоятельно учитывать масштаб, форматирование, валюту и возможные переполнения; для нескольких валют такой подход не устраняет необходимость хранить их точность отдельно.

Ситуация из практики

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

Рассматривались два варианта. Замена типа на DOUBLE PRECISION проблему не решала: это также приближённый тип. Хранение суммы в целых минимальных единицах давало точность, но усложняло работу с несколькими валютами и разными правилами округления.

Выбрали DECIMAL с явно согласованными точностью и масштабом, а правила округления зафиксировали на уровне расчётного сервиса и схемы данных. Результаты стали воспроизводимыми, а операции, которым требовалась большая точность, начали выполняться с отдельным масштабом до финального округления при выставлении счёта.

Что кандидаты часто упускают

  1. Всегда ли DECIMAL гарантирует отсутствие ошибок в денежных расчётах?

Нет. Он точно хранит значения в пределах поддерживаемых точности и масштаба, но не знает бизнес-правил округления. Если результат не помещается в заданный масштаб, дробная часть может быть округлена; при недостатке целой части возникает переполнение или ошибка. Кроме того, последовательность операций важна: раннее округление до умножения и округление только в конце могут дать разные итоги.

  1. Почему сравнение значений DOUBLE PRECISION на точное равенство считается рискованным?

Два математически равных результата могут получить немного разные внутренние представления из-за порядка операций и округлений. Поэтому для приближённых чисел обычно сравнивают абсолютную или относительную разницу с допустимым эпсилоном. Такой подход не заменяет точный тип, если сама предметная область требует точного десятичного результата.

  1. Когда целое число в минимальных денежных единицах предпочтительнее DECIMAL?

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