Сохраняйте точность с числовыми значениями

Позвольте мне повторить содержание, прежде чем мы начнем. Основная цель этой статьи — обсудить, как значения валюты могут храниться в базах данных (MySQL). Моя второстепенная цель - объяснить неточность значений с плавающей запятой. Теперь, когда мы прояснили область действия, давайте начнем. Требование простое. В нашей базе данных мы должны хранить денежные значения или числовые значения, где точность имеет решающее значение. Представленные ниже подходы или решения используют MySQL, а кодирование выполняется на Java, но я надеюсь, что основы вам понятны. Еще одно небольшое замечание: некоторые реляционные базы данных, такие как Postgres, поддерживают тип Money, но для простоты мы будем придерживаться MySQL. Давайте рассмотрим различные типы данных, которые можно использовать для хранения денежных значений:

Давайте рассмотрим различные типы данных, которые можно использовать для хранения денежных значений:

  1. Плавающий / Двойной
  2. Десятичный/Числовой
  3. Большое / Целое
  4. Варчар/Струна

Хорошо, мы составили список наших лучших решений. Чтобы было ясно, я не перечислил их по типу данных. Как видите, 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 для хранения точных представлений, но один важный вывод помимо очевидного заключается в том, что для хранения числа в виде строки требуется больше байтов. И любая арифметика над значением всегда преобразует его в число.

Хорошо, мы говорили о проблеме с точностью поплавка. Мы говорили о различных типах данных валюты. Цель достигнута. Всем хорошего дня.

Рекомендации

  1. IEEE 754 — Википедия, https://en.wikipedia.org/wiki/IEEE_754
  2. Арифметика с плавающей запятой — Википедия, https://en.wikipedia.org/wiki/Floating-point_arithmetic#Representable_numbers.2C_conversion_and_rounding
  3. Характеристики типа данных DECIMAL — MySQL, https://dev.mysql.com/doc/refman/8.0/en/precision-math-decimal-characteristics.html