обзор
Многие разработчики определили, что BigDecimal — единственный способ иметь дело с деньгами. Часто они заявляют, что, заменяя double на BigDecimal, они исправляют одну или десять ошибок. Что я нахожу неубедительным в этом, так это то, что, возможно, они могли бы исправить ошибку в обработке double и что дополнительные издержки использования BigDecimal.
Мое сравнение, когда меня попросили улучшить производительность финансового приложения, я знаю, что когда-нибудь мы удалим BigDecimal, если он там есть. (Обычно это не самый большой источник задержек, но когда мы исправляем систему, она переходит к худшему нарушителю).
BigDecimal не является улучшением
У BigDecimal много проблем, так что выбирайте сами, но уродливый синтаксис, пожалуй, худший грех.
- Синтаксис BigDecimal неестественный.
- BigDecimal использует больше памяти
- BigDecimal создает мусор
- BigDecimal намного медленнее для большинства операций (есть исключения)
Следующий тест JMH демонстрирует две проблемы с BigDecimal, ясность и производительность.
Код ядра принимает в среднем два значения.
Двойная реализация выглядит следующим образом. Примечание: необходимо использовать округление.
1
|
mp[i] = round6((ap[i] + bp[i]) / 2); |
Та же самая операция, использующая BigDecimal, не только длинная, но и много кода для навигации.
1
2
|
mp2[i] = ap2[i].add(bp2[i]) .divide(BigDecimal.valueOf(2), 6, BigDecimal.ROUND_HALF_UP); |
Это дает вам разные результаты? double имеет 15 цифр точности, а числа намного меньше 15 цифр. Если бы эти цены имели 17 цифр, это бы сработало, но не сработало бы и для бедного человека, который также должен понимать цену (то есть они никогда не будут невероятно длинными).
Производительность
Если вам приходится нести затраты на кодирование, обычно это делается из соображений производительности, но здесь это не имеет смысла.
эталонный тест | Режим | образцы | Гол | Ошибка в счете | Единицы измерения |
osMyBenchmark.bigDecimalMidPrice | thrpt | 20 | 23638.568 | 590,094 | OPS / с |
osMyBenchmark.doubleMidPrice | thrpt | 20 | 123208.083 | 2109.738 | OPS / с |
Вывод
Если вы не знаете, как использовать round в double, или ваш проект требует BigDecimal, тогда используйте BigDecimal. Но если у вас есть выбор, не просто предполагайте, что BigDecimal — верный путь.
Код
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
|
import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import java.math.BigDecimal; import java.util.Random; @State (Scope.Thread) public class MyBenchmark { static final int SIZE = 1024 ; final double [] ap = new double [SIZE]; final double [] bp = new double [SIZE]; final double [] mp = new double [SIZE]; final BigDecimal[] ap2 = new BigDecimal[SIZE]; final BigDecimal[] bp2 = new BigDecimal[SIZE]; final BigDecimal[] mp2 = new BigDecimal[SIZE]; public MyBenchmark() { Random rand = new Random( 1 ); for ( int i = 0 ; i < SIZE; i++) { int x = rand.nextInt( 200000 ), y = rand.nextInt( 10000 ); ap2[i] = BigDecimal.valueOf(ap[i] = x / 1e5); bp2[i] = BigDecimal.valueOf(bp[i] = (x + y) / 1e5); } doubleMidPrice(); bigDecimalMidPrice(); for ( int i = 0 ; i < SIZE; i++) { if (mp[i] != mp2[i].doubleValue()) throw new AssertionError(mp[i] + " " + mp2[i]); } } @Benchmark public void doubleMidPrice() { for ( int i = 0 ; i < SIZE; i++) mp[i] = round6((ap[i] + bp[i]) / 2 ); } static double round6( double x) { final double factor = 1e6; return ( long ) (x * factor + 0.5 ) / factor; } @Benchmark public void bigDecimalMidPrice() { for ( int i = 0 ; i < SIZE; i++) mp2[i] = ap2[i].add(bp2[i]) .divide(BigDecimal.valueOf( 2 ), 6 , BigDecimal.ROUND_HALF_UP); } public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include( ".*" + MyBenchmark. class .getSimpleName() + ".*" ) .forks( 1 ) .build(); new Runner(opt).run(); } } |
Ссылка: | Если BigDecimal — ответ, это, должно быть, был странный вопрос от нашего партнера JCG Питера Лоури из блога Vanilla Java . |