Какой тип данных следует использовать для обработки ERP: BigDecimal или Double в Java?

В настоящее время я разрабатываю систему ERP (планирование ресурсов предприятия), и мне нужно принять решения относительно типа данных, которые будут использоваться для обработки вычислений и других числовых значений в приложении.

Я знаю, что Java предлагает несколько вариантов обработки десятичных чисел, например BigDecimal и Double. Я понимаю, что у каждого из них есть свои преимущества и недостатки, но я хочу убедиться, что выберу наиболее подходящий из них, чтобы сохранить точность и эффективность моей системы.

Вот некоторые конкретные контексты, в которых мне нужно применить вычисления:

  • Выставление счетов и платежи: обработка цен, налогов, скидок и т. д.
  • Заработная плата и бухгалтерский учет:
  • Управление запасами: включая цены за единицу продукции и себестоимость продукции.

Я склонен использовать BigDecimal для финансовых операций из-за его точности, но меня беспокоит влияние на производительность. С другой стороны, Double может быть более эффективным с точки зрения скорости, но может привести к ошибкам округления.

Какие у вас есть рекомендации по решению этих случаев в ERP? В каких ситуациях предпочтительнее использовать BigDecimal, а когда Double? Есть ли какие-то шаблоны или лучшие практики, которым мне следует следовать?

Вам следует использовать BigDecimal для всех денежных расчетов.

David Conrad 11.06.2024 22:09

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

Gene 11.06.2024 22:22

Канонический вопрос и ответы: Не работает ли математика с плавающей запятой?

Jim Garrison 12.06.2024 01:16

Double предназначен не для десятичных чисел, а для чисел с плавающей запятой.

Mark Rotteveel 27.06.2024 12:26
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
4
4
121
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Что произойдет, если вы этого не сделаете? Неточная точность double/Double может привести к просто неверным результатам при выполнении любой математической операции. Неправильные результаты могут стоить вашей компании денег, доверия и времени на исправление неверных расчетов.

Производительность, скорее всего, не будет проблемой. Решайте проблемы с производительностью только тогда, когда они возникают, и не оптимизируйте заранее проблему, которой не существует.

Почему BigDecimal, а не long? Если необходимо как можно более справедливо разделить 10 долларов между тремя людьми, каждый из них не получит ни 3,33 доллара, ни 3,333333 доллара; вместо этого двое получат 3,33 доллара, а один — 3,34 доллара. Для вычисления правильного подразделения не требуется ничего, кроме точности в пенни, и никакой уровень точности не даст правильного подразделения, если невозможно учесть остаток в пенни.

supercat 11.06.2024 22:32

Окей, спасибо! С точки зрения производительности, действительно ли это такая большая разница?

dssof 11.06.2024 22:35

См. комментарий @supercat: «просто используйте BigDecimal» слишком упрощено и редко правильно. «просто используйте long» обычно является правильным (иногда требуется BigDecimal. Но не часто). Вы всегда должны учитывать разделение, и BD не спасет вас от этого (BD не может разделить 1 евро на 3 человек больше, чем вы можете это сделать) - и вы всегда должны учитывать, что подавляющее, подавляющее большинство ссылок на другие системы которые связаны с финансами, не могут передать ничего меньшего, чем атомная единица (например, вы не можете перевести полцента между банковскими счетами. Итак, если ваша система может их представлять... что теперь?)

rzwitserloot 12.06.2024 00:20

@rzwitserloot: COBOL имеет плохую репутацию, но его обработка фиксированных десятичных типов имела некоторые определенные преимущества, хотя в наши дни хотелось бы хранить такие вещи как длинные целые числа в масштабе десятичной степени, а не как последовательность десятичных цифр. .

supercat 12.06.2024 00:45

@supercat Это медленно, как патока, не решает проблему «делить на 3», отбрасывает банку в сторону (ладно, мы, по-видимому, решили, что степень детализации в 1 цент недостаточно точна. Но COBOL не является в любом случае бесконечно точно, это просто позволяет вам определить степень детализации. Дело в том, что вы все еще округляете. Если округление в какой-то момент приемлемо, то почему бы не округлить до 1 цента, а округлить намеренно? большинство финансовых систем уже так делают. Если округление неприемлемо, решение Кобола бесполезно).

rzwitserloot 12.06.2024 06:03

@supercat BigDecimal чуть лучше. Делить на 3 он тоже не умеет, но вместо округления может выбрасывать. Это... тоже не то, чего ты хочешь. Это защита от ошибок разработчиков, что достаточно хорошо, но вот ключевой момент: в финансовых системах округление будет происходить, и оно должно быть сознательным и явным действием. Хотите отправить 8 центов 3 людям? Тогда 2 человека получают 3 цента, а 1 человек — 2, причем невезучий выбирается случайным образом. Или каждый получает по 2 цента, а банк оставляет 2, потому что банк округляет в свою пользу. Вы не можете использовать BD для этого. Это индивидуально для каждой операции.

rzwitserloot 12.06.2024 06:05

@rzwitserloot: COBOL реализует типы с фиксированной дробью как последовательность десятичных цифр, таким образом не получая никакой выгоды от способности машин обрабатывать более широкие целочисленные типы; этот аспект репрезентации явно устарел. Однако COBOL решает проблему «разделения на три»; Я думаю, что синтаксис примерно такой: «ДЕЛИТЬ N НА D С ЧАСТНЫМ Q И ОСТАТКОМ R». Немного многословно, но округления не требуется.

supercat 12.06.2024 17:45

@supercat Это не «помогает». Люди ищут «просто», чтобы вы могли использовать такой тип данных, чтобы вы могли делать с числами все, что захотите, без необходимости сталкиваться с тем фактом, что существуют ошибки округления. В конце концов, если вы не против иметь дело с возможными ошибками округления по мере их возникновения, вы можете просто использовать long eurocents;. Однако на самом деле BD этого не делает, а кобол не «перемещает остаток сюда». Это так же легко воспроизвести в Java.

rzwitserloot 13.06.2024 00:46
Ответ принят как подходящий

Избегайте операций с плавающей запятой за деньги

Технология с плавающей запятой жертвует точностью на скорость выполнения.

В Java примитивные типы float и double являются типами с плавающей запятой. Как и их классы-обертки Float и Double. Никогда не используйте их там, где важна точность. Это значит никогда не ради денег.

Используйте либо BigDecimal, либо целые числа.

Вместо этого дробные денежные суммы должны обрабатываться одним из следующих подходов:

Таблица типов дробных денег

Тип Пример Примечания Плюсы Минусы Плавающая точка
float и double1.23d Никогда не используйте ради денег Быстрое исполнение. Удобное использование в качестве литералов. Неточно. BigDecimal new BigDecimal( "1.23" ) Точно представляет дробные суммы. Удобные методы, такие как Банковское округление. Медленный. Неуклюжий синтаксис. Целое число
int и longlong cents = new Double( 1.23d * 100 ).longValue() ; Требуется умножить дробные входные данные и разделить, чтобы получить дробные выходные данные. Быстрое исполнение. Простой тип с прямым соответствием типов в базе данных. Может сбить с толку наивного программиста и пользователя. (Обязательно обозначайте четко и последовательно, например cents или mills.) Библиотека Джода-Деньги Money money = Money.parse("USD 23.87") ; Явно создан для решения задач обработки денежных сумм и математических вычислений. Обрабатывает как фиксированную, так и переменную точность. Не стандартизировано.

BigDecimal также имеет Pro, позволяющий использовать почти бесконечные большие числа (и при этом быть точным).

user85421 11.06.2024 23:20

А как насчет количества, например, количества запасов, которое можно измерить в фунтах?

dssof 12.06.2024 03:32

Другие вопросы по теме