Format numbers correctly in Java

Asked

Viewed 379 times

2

I’m trying to use the NumberFormat to format some values in Java but it always returns the rounded value. I’ve done enough research to see if I can make it not happen, but I haven’t found anything about it.

Code:

NumberFormat formatter = new DecimalFormat("#,###.##");
System.out.println(formatter.format(10000999999999999999100.69d));

Output:

10.001.000.000.000.000.000.000

I would like it to return the correct value without rounding down type:

10.000.999.999.999.999.999.100.69d

1 answer

3


The cause of the problem

Your problem is further down. For example, look at this:

public class Main {
    public static void main(String[] args) {
        double a = 10000999999999999999100.69d;
        double b = 10001000000000000000000d;
        System.out.println(a);
        System.out.println(a == b);
    }
}

Here’s the way out:

1.0001E22
true

See here this running on ideone.

What happens is that your number has been rounded up to be stored in the double. In your case, the double is not accurate enough to store your number (see more here). The greater the magnitude of the number, the less accurate the double. The double has a 52-bit precision. Which means that numbers that need more than 52 bits to be stored will somehow be rounded.

To know exactly how yours double is being stored, we can do this:

public class Main {
    public static void main(String[] args) {
        double a = 10000999999999999999100.69d;
        String x = Long.toBinaryString(Double.doubleToRawLongBits(a));
        while (x.length() < 64) {
            x = "0" + x;
        }
        System.out.println(x);
    }
}

Here’s the way out:

0100010010000000111100010011111000001100000000110111001011001101

See here this running on the ideone.

What this pattern means?

  • 0 - Positive number.
  • 10001001000 - Exponent. That is 8 + 64 + 1024 = 1096. There is a 1023 bias (one must subtract 1023), and therefore the exponent is 1096 - 1023 = 73.
  • 0000111100010011111000001100000000110111001011001101 - The fraction is 265,248,791,818,957
  • I mean, this is:

(1 + (265.248.791.818.957 / 252)) * 273
273 + (265.248.791.818.957 / 252) * 273
273 + (265.248.791.818.957 / 252) * 252 * 221
273 + 265.248.791.818.957 * 221
273 + 265.248.791.818.957 * 2.097.152
9.444.732.965.739.290.427.392 + 556.267.034.260.709.310.464
10.000.999.999.999.999.737.856

Okay, the final number represented is 10,000,999,999,999,999,737,856. So why was 10,001,000,000,000,000,000? The answer is because, as you can see, there was loss of precision in the stored number. Therefore, considering that there was this loss, there are several different numbers that will be presented as if they were equal. And then, the algorithm that shows the double as a string, it seeks to produce in the string, the value that has the roundest decimal representation among the possible numbers that will produce it, and not the representation of the exact nominal value.

What are these various numbers that will generate this representation? We can add 1 the fraction up there and redo the calculation to find the next representable value, which is 10,001,000,000,000,001,835,008. The difference is 2,097,152 = 273-52 = 221. This also shows that by the great magnitude of the number, the accuracy of the double for this range of numbers is already so small that the difference between two distinguishable values is 2,097,152.

Whereas numbers can be rounded up or down and the nominal value is the centre of the range of values it represents, thus the margin of error for each side is 1,048,576 from the nominal value (1,048,576 = 2,097,152 / 2). There is also a preference for the number with the even fraction in case the number is equidistant between two representable points. With that, we have to all values x such that 10.000.999.999.998.689.280 < x < 10.001.000.000.000.786.432 are represented in the same way in a double and therefore indistinguishable. Of these, the number 10,001,000,000,000,000,000 is the one with the round decimal representation. And its number 10,000,999,999,999,999,999,100.69 is within this range.

How to solve

Just wear the class BigDecimal. Abandon the double if you want arbitrary accuracy. Also use class DecimalFormatSumbols along with the DecimalFormat. Here’s the code:

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.math.BigDecimal;

public class Main {
    public static void main(String[] args) {
        var formatter = new DecimalFormat("#,###.##");
        var s = new DecimalFormatSymbols();
        s.setDecimalSeparator(',');
        s.setGroupingSeparator('.');
        formatter.setDecimalFormatSymbols(s);
        var numero = new BigDecimal("10000999999999999999100.69");
        System.out.println(formatter.format(numero));
    }
}

Here’s the way out:

10.000.999.999.999.999.999.100,69

See here working on ideone.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.