JDK-8057170 : [Fmt-De] DecimalFormat produces wrong format() results in a few non HALF_* corner cases
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.text
  • Affected Version: 8,9
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • OS: generic
  • CPU: generic
  • Submitted: 2014-09-03
  • Updated: 2023-08-07
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbdUnresolved
Related Reports
Relates :  
Description
Calling DecimalFormat.format(double ...) when rounding mode is one of UP, DOWN, CEILING, FLOOR, UNNECESSARY will bring wrong rounding decision from DigitList.shouldRoundUp due to roumding-up/truncation of digits already happening in Double.toString() (more precisely sun.misc.FloatingDecimal.dtoa() called from toString()).

This will happen when dtoa() pre- rounding/truncation happens exactly at the DecimalFormat.maximumFractionDigits position

This is a long-standing bug (pre 9) that is an equivalent to JDK-7131459 for non HALF_* cases.
In that case threshold digit is not '5' (i.e. tie value) as in HALF_* modes, but '0' digit.

For example rounding at 2nd fractional digits position the input value "1.26" with a DecimalFormat.format()
call, using UP rounding mode, will WRONGLY provide 1.26 while it should provide 1.27 since best binary approximation is in fact a big greater (1.2600000000000000088817841970012523233890533447265625
 ) than 1.26.
In this case dtoa() truncated the result, thus suppressing the remaining digits that should be taken into account and which should cause rounding-up. DigitList.shouldRoundUp() should take into account pre- rounding-up/truncation from dtoa().

There 12 such errors for non HALF_* rounding modes. Below is the list of those :
// ***** Rounding errors in DigitList.ShouldRoundUp when non HALF_* rounding
// ***** happening when no more digits after the fractional rounding position
// ***** All these errors are due to sun.misc.FloatingDecimal.dtoa() 
// ***** rounding-up/truncating the digits due to constraints of IEEE-754
// ***** dtoa() is called from Double.toString() call.

DecimalFormat corner cases rounding for NON HALF_* rules
DecimalFormat rounding at 2nd Fractional position

=============== UP rounding mode "on threshold" cases ==============
--------------- Positive Values -------------
Value above threshold at rounding position : 1.26
Default output for  1.26 -> 1.26
BigDecimal output for 1.26 -> 1.2600000000000000088817841970012523233890533447265625
Best binary approximation (abs value) above threshold : truncated.
==> SHOULD be rounded (UP). Expected 1.27
DecimalFormat.format() output for 1.26 -> 1.26

--------------- Negative Values -------------
==> UP rounding-mode similar on negative values (-1.1 --> -2) ...
Value above threshold at rounding position : -1.26
Default output for  -1.26 -> -1.26
BigDecimal output for -1.26 -> -1.2600000000000000088817841970012523233890533447265625
Best binary approximation (abs value) above threshold : truncated.
==> SHOULD be rounded (UP). Expected -1.27
DecimalFormat.format() output for -1.26 -> -1.26

End =============== UP rounding mode "on threshold" cases ============== End

=============== DOWN rounding mode "on threshold" cases ==============
--------------- Positive Values -------------
Value below threshold at rounding position : 1.24
Default output for  1.24 -> 1.24
BigDecimal output for 1.24 -> 1.2399999999999999911182158029987476766109466552734375
Best binary approximation (abs value) below threshold : rounded-up.
==> SHOULD be rounded (DOWN). Expected 1.23
DecimalFormat.format() output for 1.24 -> 1.24

--------------- Negative Values -------------
==> DOWN rounding-mode similar on negative values (-1.1 --> -1) ...
Value below threshold at rounding position : -1.24
Default output for  -1.24 -> -1.24
BigDecimal output for -1.24 -> -1.2399999999999999911182158029987476766109466552734375
Best binary approximation (abs value) below threshold : rounded-up.
==> SHOULD be rounded (DOWN). Expected -1.23
DecimalFormat.format() output for -1.24 -> -1.24

End =============== DOWN rounding mode "on threshold" cases ============== End

=============== CEILING rounding mode "on threshold" cases ==============
--------------- Positive Values -------------
Value above threshold at rounding position : 1.26
Default output for  1.26 -> 1.26
BigDecimal output for 1.26 -> 1.2600000000000000088817841970012523233890533447265625
Best binary approximation (abs value) above threshold : truncated.
==> SHOULD be rounded (CEILING). Expected 1.27
DecimalFormat.format() output for 1.26 -> 1.26

--------------- Negative Values -------------
==> CEILING rounding-mode different on negative values (-1.1 --> -1) ...
Value below threshold at rounding position : -1.24
Default output for  -1.24 -> -1.24
BigDecimal output for -1.24 -> -1.2399999999999999911182158029987476766109466552734375
Best binary approximation (abs value) below threshold : rounded-up.
==> SHOULD be rounded (CEILING). Expected -1.23
DecimalFormat.format() output for -1.24 -> -1.24

End =============== CEILING rounding mode "on threshold" cases ============== End

=============== FLOOR rounding mode "on threshold" cases ==============
--------------- Positive Values -------------
Value below threshold at rounding position : 1.24
Default output for  1.24 -> 1.24
BigDecimal output for 1.24 -> 1.2399999999999999911182158029987476766109466552734375
Best binary approximation (abs value) below threshold : rounded-up.
==> SHOULD be rounded (FLOOR). Expected 1.23
DecimalFormat.format() output for 1.24 -> 1.24

--------------- Negative Values -------------
==> FLOOR rounding-mode different on negative values (-1.1 --> -2) ...
Value above threshold at rounding position : -1.26
Default output for  -1.26 -> -1.26
BigDecimal output for -1.26 -> -1.2600000000000000088817841970012523233890533447265625
Best binary approximation (abs value) above threshold : truncated.
==> SHOULD be rounded (FLOOR). Expected -1.27
DecimalFormat.format() output for -1.26 -> -1.26

End =============== FLOOR rounding mode "on threshold" cases ============== End

=============== UNNECESSARY rounding mode "on threshold" cases ==============
--------------- Positive Values -------------
Value below threshold at rounding position : 1.24
Default output for  1.24 -> 1.24
BigDecimal output for 1.24 -> 1.2399999999999999911182158029987476766109466552734375
Best binary approximation (abs value) below threshold : rounded-up.
==> SHOULD throw ArithmeticException !
DecimalFormat.format() output for 1.24 -> 1.24

Value above threshold at rounding position : 1.26
Default output for  1.26 -> 1.26
BigDecimal output for 1.26 -> 1.2600000000000000088817841970012523233890533447265625
Best binary approximation (abs value) above threshold : truncated.
==> SHOULD throw ArithmeticException !
DecimalFormat.format() output for 1.26 -> 1.26

--------------- Negative Values -------------
==> UNNECESSARY rounding-mode similar on negative values (ArithmeticException thrown) ...
Value below threshold at rounding position : -1.24
Default output for  -1.24 -> -1.24
BigDecimal output for -1.24 -> -1.2399999999999999911182158029987476766109466552734375
Best binary approximation (abs value) below threshold : rounded-up.
==> SHOULD throw ArithmeticException !
DecimalFormat.format() output for -1.24 -> -1.24

Value above threshold at rounding position : -1.26
Default output for  -1.26 -> -1.26
BigDecimal output for -1.26 -> -1.2600000000000000088817841970012523233890533447265625
Best binary approximation (abs value) above threshold : truncated.
==> SHOULD throw ArithmeticException !
DecimalFormat.format() output for -1.26 -> -1.26

End =============== UNNECESSARY rounding mode "on threshold" cases ============== End



Comments
The reported behavior still exists in JDK 20.
09-08-2022

work on stand-by at the moment
01-10-2015

Cause known, fix understood
24-10-2014