В настоящее время я разрабатываю систему ERP (планирование ресурсов предприятия), и мне нужно принять решения относительно типа данных, которые будут использоваться для обработки вычислений и других числовых значений в приложении.
Я знаю, что Java предлагает несколько вариантов обработки десятичных чисел, например BigDecimal и Double. Я понимаю, что у каждого из них есть свои преимущества и недостатки, но я хочу убедиться, что выберу наиболее подходящий из них, чтобы сохранить точность и эффективность моей системы.
Вот некоторые конкретные контексты, в которых мне нужно применить вычисления:
Я склонен использовать BigDecimal для финансовых операций из-за его точности, но меня беспокоит влияние на производительность. С другой стороны, Double может быть более эффективным с точки зрения скорости, но может привести к ошибкам округления.
Какие у вас есть рекомендации по решению этих случаев в ERP? В каких ситуациях предпочтительнее использовать BigDecimal, а когда Double? Есть ли какие-то шаблоны или лучшие практики, которым мне следует следовать?
Также делайте все возможное, чтобы абстрагировать всю денежную арифметику за счет внутреннего API, реализованного в одном месте, чтобы при необходимости было сравнительно легко подключить другой вид арифметики, не говоря уже об улучшении тестирования, поддержке аудита и других преимуществах.
Канонический вопрос и ответы: Не работает ли математика с плавающей запятой?
Double предназначен не для десятичных чисел, а для чисел с плавающей запятой.




Для всевозможных расчетов, связанных с деньгами, было бы неосторожно не использовать BigDecimal. Пожалуйста, используйте BigDecimal для всех расчетов.
Что произойдет, если вы этого не сделаете? Неточная точность double/Double может привести к просто неверным результатам при выполнении любой математической операции. Неправильные результаты могут стоить вашей компании денег, доверия и времени на исправление неверных расчетов.
Производительность, скорее всего, не будет проблемой. Решайте проблемы с производительностью только тогда, когда они возникают, и не оптимизируйте заранее проблему, которой не существует.
Почему BigDecimal, а не long? Если необходимо как можно более справедливо разделить 10 долларов между тремя людьми, каждый из них не получит ни 3,33 доллара, ни 3,333333 доллара; вместо этого двое получат 3,33 доллара, а один — 3,34 доллара. Для вычисления правильного подразделения не требуется ничего, кроме точности в пенни, и никакой уровень точности не даст правильного подразделения, если невозможно учесть остаток в пенни.
Окей, спасибо! С точки зрения производительности, действительно ли это такая большая разница?
См. комментарий @supercat: «просто используйте BigDecimal» слишком упрощено и редко правильно. «просто используйте long» обычно является правильным (иногда требуется BigDecimal. Но не часто). Вы всегда должны учитывать разделение, и BD не спасет вас от этого (BD не может разделить 1 евро на 3 человек больше, чем вы можете это сделать) - и вы всегда должны учитывать, что подавляющее, подавляющее большинство ссылок на другие системы которые связаны с финансами, не могут передать ничего меньшего, чем атомная единица (например, вы не можете перевести полцента между банковскими счетами. Итак, если ваша система может их представлять... что теперь?)
@rzwitserloot: COBOL имеет плохую репутацию, но его обработка фиксированных десятичных типов имела некоторые определенные преимущества, хотя в наши дни хотелось бы хранить такие вещи как длинные целые числа в масштабе десятичной степени, а не как последовательность десятичных цифр. .
@supercat Это медленно, как патока, не решает проблему «делить на 3», отбрасывает банку в сторону (ладно, мы, по-видимому, решили, что степень детализации в 1 цент недостаточно точна. Но COBOL не является в любом случае бесконечно точно, это просто позволяет вам определить степень детализации. Дело в том, что вы все еще округляете. Если округление в какой-то момент приемлемо, то почему бы не округлить до 1 цента, а округлить намеренно? большинство финансовых систем уже так делают. Если округление неприемлемо, решение Кобола бесполезно).
@supercat BigDecimal чуть лучше. Делить на 3 он тоже не умеет, но вместо округления может выбрасывать. Это... тоже не то, чего ты хочешь. Это защита от ошибок разработчиков, что достаточно хорошо, но вот ключевой момент: в финансовых системах округление будет происходить, и оно должно быть сознательным и явным действием. Хотите отправить 8 центов 3 людям? Тогда 2 человека получают 3 цента, а 1 человек — 2, причем невезучий выбирается случайным образом. Или каждый получает по 2 цента, а банк оставляет 2, потому что банк округляет в свою пользу. Вы не можете использовать BD для этого. Это индивидуально для каждой операции.
@rzwitserloot: COBOL реализует типы с фиксированной дробью как последовательность десятичных цифр, таким образом не получая никакой выгоды от способности машин обрабатывать более широкие целочисленные типы; этот аспект репрезентации явно устарел. Однако COBOL решает проблему «разделения на три»; Я думаю, что синтаксис примерно такой: «ДЕЛИТЬ N НА D С ЧАСТНЫМ Q И ОСТАТКОМ R». Немного многословно, но округления не требуется.
@supercat Это не «помогает». Люди ищут «просто», чтобы вы могли использовать такой тип данных, чтобы вы могли делать с числами все, что захотите, без необходимости сталкиваться с тем фактом, что существуют ошибки округления. В конце концов, если вы не против иметь дело с возможными ошибками округления по мере их возникновения, вы можете просто использовать long eurocents;. Однако на самом деле BD этого не делает, а кобол не «перемещает остаток сюда». Это так же легко воспроизвести в Java.
Технология с плавающей запятой жертвует точностью на скорость выполнения.
В Java примитивные типы float и double являются типами с плавающей запятой. Как и их классы-обертки Float и Double. Никогда не используйте их там, где важна точность. Это значит никогда не ради денег.
BigDecimal, либо целые числа.Вместо этого дробные денежные суммы должны обрабатываться одним из следующих подходов:
long). Чтобы отслеживать доллар США с точностью до десятых цента, умножьте его на тысячу, чтобы получить милли (₥).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, позволяющий использовать почти бесконечные большие числа (и при этом быть точным).
А как насчет количества, например, количества запасов, которое можно измерить в фунтах?
Вам следует использовать
BigDecimalдля всех денежных расчетов.