Summary
Correct DecimalFormat
’s implementation to handle an edge case when formatting a fractional floating-point value would return a zero string, violating the UP, CEILING, and FLOOR RoundingMode
contracts.
Problem
DecimalFormat
, when formatting a floating-point value, will first make a conversion from a floating-point value to a string representation, (from here on, out referred to as s). s is then truncated and rounded under the DecimalFormat
pattern and RoundingMode
to create the resultant string returned.
In this particular edge case, if the maximum fractional digits given by the DecimalFormat
pattern is less than the amount of leading zeroes in the fractional portion of s, DecimalFormat
incorrectly returned a zero string, instead of considering rounding. By not considering rounding, formatting certain floating-point values violated the UP, CEILING, and FLOOR RoundingMode
contracts. Specifically, the stipulation: "Note that this rounding mode never decreases the magnitude of the calculated value".
For example, consider a DecimalFormat
with pattern “0.0” and RoundingMode.UP
which formats the numerical value .001
. This numerical value, which is a representation of the floating-point value 0.001000000000000000020816681711721685132943093776702880859375
is converted to the decimal string “0.001” by DecimalFormat
. The existing implementation assumes it can return zero as the fractional precision (1) given by the DecimalFormat
pattern is less than the count of fractional leading zeros (2) in the decimal string. However, returning the formatted string "0.0" violates the RoundingMode.UP
contract and should have been "0.1".
Solution
Ensure that rounding is considered, even if the maximum fractional digits allowed by the DecimalFormat
pattern is less than the leading fractional zeroes given by s for formatting a fractional floating-point value.
Given the double value 0.00010000000000000000479217...
represented by the numerical value 0.0001
,
the following is an example of the behavioral changes under every RoundingMode with a "0.00" pattern DecimalFormat
. Unless marked as different, the results are the same as before the change.
0.0001 formatted with 0.00 and mode UP gives 0.01 (previously, 0.00)
0.0001 formatted with 0.00 and mode DOWN gives 0.00
0.0001 formatted with 0.00 and mode CEILING gives 0.01 (previously, 0.00)
0.0001 formatted with 0.00 and mode FLOOR gives 0.00
0.0001 formatted with 0.00 and mode HALF_UP gives 0.00
0.0001 formatted with 0.00 and mode HALF_DOWN gives 0.00
0.0001 formatted with 0.00 and mode HALF_EVEN gives 0.00
-0.0001 formatted with 0.00 and mode UP gives -0.01 (previously, -0.00)
-0.0001 formatted with 0.00 and mode DOWN gives -0.00
-0.0001 formatted with 0.00 and mode CEILING gives -0.00
-0.0001 formatted with 0.00 and mode FLOOR gives -0.01 (previously, -0.00)
-0.0001 formatted with 0.00 and mode HALF_UP gives -0.00
-0.0001 formatted with 0.00 and mode HALF_DOWN gives -0.00
-0.0001 formatted with 0.00 and mode HALF_EVEN gives -0.00
Note: This change does not affect the case where the DecimalFormat
maximum fractional digits is equivalent to the leading fractional zeroes given by s. Rounding behavior for that case remains the same. For example, the double value 0.0500000000000000027755...
given by the numerical value 0.05
would still format to 0.1
under a "0.0" DecimalFormat pattern with HALF_UP
. (This behavior correctly occurs before and after this change).
Specification
N/A (behavioral change only)
- csr of
-
JDK-8174722 Wrong behavior of DecimalFormat with RoundingMode.UP in special case
-
- In Progress
-