Pregunta

While working with DecimalFormat, I was puzzled by its behavior when rounding numbers with fixed-point patterns. To make things more specific:

double n = 2082809.080589735D;

// Output the exact value of n
System.out.println("value:       " + new BigDecimal(n).toPlainString());

System.out.println("double:      " + n);

DecimalFormat format = new DecimalFormat("0.00000000");

System.out.println("format:      " + format.format(n));
System.out.println("format (BD): " + format.format(new BigDecimal(n)));

The output of this snippet is:

value:       2082809.080589734949171543121337890625
double:      2082809.080589735
format:      2082809.08058974
format (BD): 2082809.08058973

From the first output line we notice that the actual value is below the half-way point between 2082809.08058973 and 2082809.08058974 (...49...). Despite this, DecimalFormat rounds the value upwards when provided with a double argument.

Other values are rounded downwards:

value:       261285.2738465850125066936016082763671875
double:      261285.273846585
format:      261285.27384658
format (BD): 261285.27384659

This does not happen in all cases:

value:       0.080589734949171543121337890625
double:      0.08058973494917154
format:      0.08058973
format (BD): 0.08058973

value:       0.2738465850125066936016082763671875
double:      0.2738465850125067
format:      0.27384659
format (BD): 0.27384659

It seems to me that the formatted string for a double is rounded using half-even rounding based on an imprecise decimal value produced with something along the lines of Double.toString(), rather than the actual mathematical value represented by the double in question. When the formatted precision is very close to (or more than) the precision provided by the double type, things start becoming somewhat random.

In all the cases presented above, formatting the corresponding BigDecimal seems to perform the rounding as expected.

I was unable to find any specification describing the proper behavior of DecimalFormat in this case.

  • Is this behavior documented somewhere?

  • From a correctness point of view, wouldn't rounding the actual mathematical value be preferable?

    I understand that people writing 1.0...35 would (naively?) expect it to be rounded to 1.0...4, but 1.0...35 may not even be representable in any primitive data type available in Java...

EDIT:

I have submitted a report to Oracle - hopefully they will be able to either fix or clarify this issue.

¿Fue útil?

Solución

This was apparently a known issue that has been fixed in Java 8.

DecimalFormat in Java 8 is supposed to always perform the rounding correctly, regardless of the format chosen by the developer. Naturally, this results in slightly incompatible behavior when compared to Java 7 - whether this is important or not would depend on the specifics of each application.

Otros consejos

According to the Java Language Specification, java uses IEEE 754 - 1985, while you (from the comments asumed IEEE754 - 2008)

The floating-point types are float and double, which are conceptually associated with the single-precision 32-bit and double-precision 64-bit format IEEE 754 values and operations as specified in IEEE Standard for Binary Floating-Point Arithmetic, ANSI/IEEE Standard 754-1985 (IEEE, New York).

Use setRoundingMode() of DecimalFormat to specify the rounding behavior.

Is this behavior documented somewhere?

See javadoc of DecimalFormat and the detailed in RoundingMode And in IEEE 754 - 1985 and in JLS (first statemen in this answer)

Default, as documented is the Half even rounding mode. HALF_EVEN

public static final RoundingMode HALF_EVEN

Rounding mode to round towards the "nearest neighbor" unless both neighbors are equidistant, in which case, round towards the even neighbor. Behaves as for RoundingMode.HALF_UP if the digit to the left of the discarded fraction is odd; behaves as for RoundingMode.HALF_DOWN if it's even. Note that this is the rounding mode that statistically minimizes cumulative error when applied repeatedly over a sequence of calculations. It is sometimes known as "Banker's rounding," and is chiefly used in the USA. This rounding mode is analogous to the rounding policy used for float and double arithmetic in Java.

It looks like this could be due to double rounding.

The output marked "double" looks like it's correctly rounded to 16 digits. The output marked "format" looks like it's rounded from the 16 digit value (rounded half to even).

(I'd need to see more examples to verify this.)

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top