Сохраняйте точность с числовыми значениями
Позвольте мне повторить содержание, прежде чем мы начнем. Основная цель этой статьи — обсудить, как значения валюты могут храниться в базах данных (MySQL). Моя второстепенная цель - объяснить неточность значений с плавающей запятой. Теперь, когда мы прояснили область действия, давайте начнем. Требование простое. В нашей базе данных мы должны хранить денежные значения или числовые значения, где точность имеет решающее значение. Представленные ниже подходы или решения используют MySQL, а кодирование выполняется на Java, но я надеюсь, что основы вам понятны. Еще одно небольшое замечание: некоторые реляционные базы данных, такие как Postgres, поддерживают тип Money, но для простоты мы будем придерживаться MySQL. Давайте рассмотрим различные типы данных, которые можно использовать для хранения денежных значений:
Давайте рассмотрим различные типы данных, которые можно использовать для хранения денежных значений:
Хорошо, мы составили список наших лучших решений. Чтобы было ясно, я не перечислил их по типу данных. Как видите, Float и Double — это два разных типа данных (Double хранит значения чисел с плавающей запятой двойной точности), но то, что они привносят в таблицу в рамках этой темы, довольно простое. Теперь начинается захватывающая часть.
Плавающий / Двойной
Если точность является требованием, это может быть нет; почему может быть? позволь мне объяснить. Рекомендуется избегать использования этого типа данных для сохранения денежных значений. Почему float и double не так точны, как вы думаете?
Давайте посмотрим на этот небольшой фрагмент кода.
class HelloWorld { public static void main(String[] args) { double total = 0.2; System.out.println(“Initial Value : “ + total); for (int i = 0; i < 10; i++) { total += 0.2; } System.out.println(“Total Value : “ + total); } }
Мы увеличиваем начальное значение десять раз с добавлением, и мы получаем результат, подобный следующему:
Initial Value : 0.2 Total Value : 2.1999999999999997
Мы ожидали 2
, а получили 2.1999999999999997
. Теперь у нас есть проблема; если мы урежем значение, мы получим 2.1
или если мы округлим его, мы получим 2.2
В любом случае, мы имеем близкую к 0.1
потерю точности (или потерю значимости)
Хорошо, почему это происходит? Прежде чем я укажу вам на решение, кое-что, что вам нужно знать, это
Поплавки согласно IEEE 754 всегда были бинарными.
Термин float относится к десятичной точке «плавает». Например, ниже приведены все разные показатели степени с одним и тем же целым числом:
1. 10.25 is 1025 x 10^(-2) 2. 0.15 is 15 x 10^(-2)
Но я сказал, что числа с плавающей запятой бинарны, верно? Да, компьютеры мыслят бинарно. Так что это что-то вроде
1. 10.25 is 164 x 2^(–4) which is 10.25 2. 0.15 is 168884986026394 x 2^(–50) which is close to 0.15
Компьютеры используют двоичные числа, потому что они быстрее справляются с ними и потому что небольшая ошибка обычно может быть проигнорирована в большинстве вычислений. Еще один важный момент, который следует отметить, заключается в том, что это не связано с двоичным кодом. Например, можем ли мы точно представить число, подобное (1/3), в базе 10? Вам нужно округлить примерно до 0,33, и вы не ожидаете, что 0,33 + 0,33 + 0,33 будет равно 1.
Хорошо, теперь объяснение. Я просто скопирую ответ, который прекрасно все объясняет.
Выдержка из Википедии:
Имеет ли рациональное число завершающее расширение, зависит от основания. Например, в базе 10 число 1/2 имеет завершающее расширение (0,5), а число 1/3 — нет (0,333…). В базе 2 заканчиваются только рациональные числа со знаменателем, равным степени 2 (например, 1/2 или 3/16). Любое рациональное число со знаменателем, имеющим простой делитель, отличный от 2, будет иметь бесконечное двоичное разложение. Это означает, что числа, которые кажутся короткими и точными при записи в десятичном формате, могут нуждаться в аппроксимации при преобразовании в двоичные числа с плавающей запятой. Например, десятичное число 0,1 не может быть представлено в виде двоичного числа с плавающей запятой любой конечной точности; точное двоичное представление будет иметь бесконечную последовательность «1100»:
e = −4; s = 1100110011001100110011001100110011…,
где, как и ранее, s – это мантиссы, а e – показатель степени.
При округлении до 24 бит это становится
e = −4; s = 110011001100110011001101,
что на самом деле 0,100000001490116119384765625 в десятичном виде.
Float использует 24-битную мантиссу, которая содержит все значащие цифры. Это означает, что он имеет точность около семи цифр (поскольку 2^(24) составляет около 16 миллионов), а Double использует 53-битную мантиссу, поэтому он может точно хранить около 16 цифр.
Подводя итог, можно сказать, что результат вычисления с плавающей запятой часто округляется, чтобы вернуться к его конечному представлению. Эта ошибка округления является определяющей характеристикой вычислений с плавающей запятой. Предположим, вы хотите выполнить определенный уровень сложного умножения. Это повлияет на ваш поток вычислений и окончательные значения, если не принять меры (это не ограничивается умножением; другие арифметические операции будут иметь тот же эффект, хотя и в меньшем масштабе для очевидные причины).
Десятичный/числовой
Это один из лучших способов сохранить валюту без каких-либо потерь. Это не сложно понять.
decimal(15,2)
15 — точность (общая длина значения, включая десятичные разряды), а 2 — количество знаков после запятой; конечно, длина и точность могут быть определены в соответствии с вашими потребностями. Предположим, ваше приложение должно обрабатывать денежные суммы до триллиона долларов. В этом случае должно работать следующее: 13,2 и Если вы должны придерживаться GAAP (общепринятых принципов бухгалтерского учета), используйте 4 для точности, например 13,4.
BigInt/целое
Другой способ — сохранить его как целое число. Единственный вывод заключается в том, что вы должны выполнить расчет. Почему? Поскольку десятичных разрядов нет, вы должны сохранять значения, умножая их на 100 или 1000, в зависимости от желаемого уровня точности. Целое число (INT) имеет диапазон со знаком от -2147483648 до 2147483647 и диапазон без знака от 0 до 4294967295. В определении столбца можно указать, является ли целое число знаковым или беззнаковым. Диапазон со знаком для Bigint составляет от -9223372036854775808 до 9223372036854775807, а диапазон без знака положительный. Unsigned имеет диапазон значений от 0 до 18446744073709551615. Более подробная информация доступна в документации MySQL. Этого достаточно для бизнес-приложений общего назначения для хранения денежных значений, но это налагает дополнительную нагрузку при работе с дробными значениями. Не рекомендуется, но это зависит от ваших потребностей и осуществимости, что относится ко всем решениям, которые я перечислил здесь.
ВАРЧАР
Я просто добавлю это в список для полноты картины: вы можете использовать VARCHAR для хранения точных представлений, но один важный вывод помимо очевидного заключается в том, что для хранения числа в виде строки требуется больше байтов. И любая арифметика над значением всегда преобразует его в число.
Хорошо, мы говорили о проблеме с точностью поплавка. Мы говорили о различных типах данных валюты. Цель достигнута. Всем хорошего дня.
Рекомендации
- IEEE 754 — Википедия, https://en.wikipedia.org/wiki/IEEE_754
- Арифметика с плавающей запятой — Википедия, https://en.wikipedia.org/wiki/Floating-point_arithmetic#Representable_numbers.2C_conversion_and_rounding
- Характеристики типа данных DECIMAL — MySQL, https://dev.mysql.com/doc/refman/8.0/en/precision-math-decimal-characteristics.html