Comments 15
Спасибо! Было полезно!
В общем советы полезные, но по поводу хранения денежных значений в long — как минимум странный.
BigDecimal ведь нужен не "для красоты при выводе", это прежде всего для корректности операций над числами с плавающей точкой.
При использовании примитивов есть большая вероятность получить неожиданный результат, что весьма критично для денежных операций. Например, данное выражение будет ложным: 0.3 + 0.6 == 0.9, т.к. сумма будет равна 0.8999999999999999. Связано это не с конкретным языком программирования, а с особенностью представления чисел с плавающей точкой в двоичной системе счисления, где некоторые числа просто невозможно точно представить. BigDecimal как раз и призван решать эту проблему.
так идея-то в том, чтобы не использовать дробные типы. Считать в копейках. тогда 30+60 будет равно 90. а в выводе ендюзеру покажете 0,9 руб )
а теперь подумаем над операцией деления.
мне нужно пересчитывать юани в рубли и обратно по курсу 1 : 11.3
;)
скейл ап. установить точность единую для всех валют. 6 или 9. и тогда любое деление будет проходить в рамках целых чисел. Так лампорты работают в солана, например.
опять же, предложенные в сабже методы оптимизаций дают заметный эффект на высоконагруженных системах. Если в стандартном бизнес сервисе применять эти подходы - пользователи и метрики вряд ли обнаружат значительные изменения. Я думаю, что это как клин код для обычных и маст хэв для высоконагруженных систем.
Согласен с вашими доводами. Про странность совета погорячиться)
Тем не менее для обычных сервисов такая оптимизация все-таки избыточна и не сказать что клин код, т.к. внесет больше путанцы с округлением, чем прирост производительности в сравнении с BigDecimal.
Нечто подобное было во времена мидлетов в JavaME для кнопочных телефонов — там поначалу плавающей точки не было вообще и приходилось самим изобретать велосипед.
это прекрасное предложение! только придется всю "арифметику" для них реализовать.
либо воспользоваться готовой библиотекой.
но будет ли этой быстрее BigDecimal? кто-то проверял?
В apache.commons-math есть Fraction и BigFraction, которые реализуют этот подход.
Но это не то, о чем пишет автор статьи. Здесь используется представление десятичной дроби в виде двух отдельных чиел — числителя и знаменателя для обыкновенной дроби. Никакой путаницы здесь не возникает, но и накладывает расходы на вычисления.
В статье же предлагается использовать один long, который просто представляет копейки. Этот подход самый оптимальный по производительности, но заморочен.
Вот бенчмарки Fraction в сравнении с BigDecimal:@Benchmarkpublic void fractionCall(Blackhole blackhole) { Fraction result = new Fraction(1.2) .multiply(new Fraction(0.4)) .divide(new Fraction(0.8)) .multiply(new Fraction(0.6)) .add(new Fraction(1.25)) .divide(new Fraction(0.75)); blackhole.consume(result);}
---
Benchmark Mode Cnt Score Error Units
bigDecimalCall thrpt 150 3825.518 ± 53.397 ops/ms
bigFractionCall thrpt 150 114.674 ± 2.151 ops/ms
fractionCall thrpt 150 1717.929 ± 10.849 ops/ms
bigDecimalCall avgt 150 ≈ 10⁻⁴ ms/op
bigFractionCall avgt 150 0.009 ± 0.001 ms/op
fractionCall avgt 150 0.001 ± 0.001 ms/op
bigDecimalCall ss 150 0.030 ± 0.023 ms/op
bigFractionCall ss 150 0.143 ± 0.088 ms/op
fractionCall ss 150 0.022 ± 0.003 ms/op
-del-
вообще, это, конечно рабочий вариант. мы, действительно, можем считать, что последние 6-9 знаков в long - это наша дробная часть, а все, что левее - целая часть. и, при этом, все хранить в "копейках". тогда мы всегда можем делать одинаковое округление в последнем знаке и оставаться в рамках целых чисел.
но тут нас ожидает другая проблема: у нас не такой уж большой максимум.
сделки на "сотни миллионов" потребуют отдельной "ветки" в логике обработки, которая будет работать медленнее основной.
а ведь "сотни миллионов" - это не так уж и много.
в общем, предложенная "оптимизация" находится где-то сильно справа на кривой "шипилева".
туда стоит лезть только будучи на 100% уверенным, что "тормозит" именно арифметика, а не условный I/O
Автор написал, что хранить в копейках например.
Это вполне корректный вариант, и не нужен bigdecimal, и все операции происходить с целыми числами, тогда не будет никаких неожиданностей.
у меня курс 1 к 3
(1 "доллар" за 3 "рубля")
сколько "долларов" я могу получить за 10 "рублей"?
(ответ 333 "цента" - совершенно не верный)
Советы полезные.
Но сам GraalVM - штука спорная, имхо. Стоит прибегать, только если вообще никаких других вариантов не осталось.
Java ведь изначально задумалась как прямо противоположное тому, что делает GraalVM (один код работает везде, без разных бинарников под каждую платформу). И теперь, когда сову попытались натянуть на глобус, внезапно оказывается, что постоянно всплывают неудобные моменты.
Уж если нужен бинарник, пожалуй, стоит использовать язык, который под это и создавался. Тот же Go.
ТОП-10+1 «Золотых правил» оптимизаций Java 21+: как заставить JIT петь, а GraalVM — летать