-
Notifications
You must be signed in to change notification settings - Fork 18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Difference in RoundingMode.HALF_UP rounded value for 0.663125 at scale 5 #17
Comments
please try
The scale you used (5) meant that the 6th decimal place was truncated |
final double input = 0.663125;
final double expected = Precision.round(input, 5, RoundingMode.HALF_UP.ordinal()); // 0.66313
final var decimalArithmetic = Scales.getScaleMetrics(6).getArithmetic(RoundingMode.HALF_UP);
final double actual = decimalArithmetic.toDouble(decimalArithmetic.fromDouble(input)); // 0.663125
assertThat(actual).isEqualTo(expected); fails differently...
|
Hi Short answer is: this is expected behaviour in decimal4j. The longer answer is an explanation and some alternative ways to round a double with decimal4j. Explanation Firstly --- this is a very good question, thanks for stating it here. The problem that Is highlighted here is more broadly the problem of 'rounding a double value' or more generally 'rounding a binary floating point value to a decimal precision'. To see this let’s look into the input value. If printed to its full precision the value is actually The value can be obtained via new BigDecimal(0.663125).toPlainString(); Now you need to know that decimal4j looks at the exact value representation of a double value when it is converted — so it produces the correct result when rounding above value down to The big question — and source of confusion — is that Java prints this value as To reiterate, the decimal4j fromDouble(..) conversion methods operate with the binary exact value and not with the string representation of that value. Alternatives The most straight forward way to round the above value according to its string representation is to input it as a string: DecimalArithmetic arith = Scales.getScaleMetrics(5).getArithmetic(RoundingMode.HALF_UP);
arith.toDouble(arith.parse(String.valueOf(0.663125))); This is not always desirable as it creates a string object and therefore garbage. This can be prevented by using a StringBuilder that is reused: StringBuilder stringBuilder = new StringBuilder(64);
stringBuilder.setLength(0);
stringBuilder.append(0.663125);
DecimalArithmetic arith = Scales.getScaleMetrics(5).getArithmetic(RoundingMode.HALF_UP);
arith.toDouble(arith.parse(stringBuilder, 0, stringBuilder.length())); A big drawback of this is currently that decimal4j does not support scientific notation, so it will not work for all double values. Parsing scientifically printed double values has been requested here and may be supported in future decimal4j versions. A last option to round a value more intuitively is to use a 2 step rounding approach:
For our example this would look for instance like this: DecimalArithmetic arith8 = Scales.getScaleMetrics(8).getRoundingHalfEvenArithmetic();
DecimalArithmetic arith5 = Scales.getScaleMetrics(5).getArithmetic(RoundingMode.HALF_UP);
long unscaled8 = arith8.fromDouble(0.663125);//rounding off the noise
long unscaled5 = arith5.fromUnscaled(unscaled8, arith8.getScale());//actual rounding happens here!
double rounded = arith5.toDouble(unscaled5); The scale for noise rounding should be at least 1 more than the actual precision, our favourite choice is 3 extra digits. Note however that the extra precision means a lower maximum value and one should be careful not to cause overflows! Source Code All code above is available as a unit test here: |
This test comparing rounding of a value with decimal4j 1.0.3 vs commons-math3 3.6.1 fails
output:
The text was updated successfully, but these errors were encountered: