Question

I bet there was a similar question here on SO, I just could not find it. The question is about fixing java math inaccuracy, when I get for example a number like 235.00000002 or 9875.999999997. Sure as a hell, for a human these two actually mean 235 and 9876, when a certain accuracy threshold is given, let's call it "5 zeros or nines".

But it's not only about the digits to the right of the decimal point, the same rule should also apply to numbers like 23500000001.0 and 5699999999.0

Any ideas/libraries?

Update: Looks like I was wrong saying it's a known topic and a matter of minutes until someone pops up with a known library. So here is the logic I expect: For a given number when N consecutive zeroes or nines are encountered in its string representation, then these zeroes and nines are rounded together with the rest of the number to the right of the rightmost "0" or "9". Foe example, when N=5, then the number 239999995.546 becomes 240000000.0, the number 34.0000007 becomes 34 as well as 340000.007 becomes 340000.

Update 2: That's what happens when in a hurry or paying not enough attention to the question. Sorry. I'm talking about "human" rounding. A good example would be comparing the output of df and df -h on a linux box. As for "inaccuracy" I was talking about, please run the following code:

double d = 1000.0;
double mult = 0.12351665;
System.out.println(d * mult / mult);

The response is definitely not the one you'd show in a shopping cart. In my case the situation is even worse, it's not only the money I deal with, it can be anything - percentage, big numbers, small fractions, and all of them as a result of relatively heavy math computations. So I'm talking about the rounding that makes sense for a human.

I'm already done with the code, but there is still a chance that someone did it better.

Was it helpful?

Solution

I played around a little with the scientific notation as I already suggested in the comments. Here is what I came up with:

public static double simplify(Number n) {
    String numberString = String.format("%e", n);
    int indexE = numberString.indexOf('e');
    String baseValue = numberString.substring(0, indexE);
    String exponent = numberString.substring(indexE + 1);
    double base = Double.parseDouble(baseValue);
    int    exp = Integer.parseInt(exponent);

    return base * Math.pow(10, exp);
}

I used all numbers I found in your question and added a negative value as well to test it.

public static void main(String[] args) {
    Number[] ns = new Number[]{
            239999995.546,
            239989995.546,
               340000.007,
                   34.0000007,
           5699999999.0,
                  235.00000002,
                 9875.999999997,
                -4334.345345,
          23500000001.0,
                    0.30000007,
                   -0.053999949
    };
    DecimalFormat df = new DecimalFormat("0.#####");
    for(Number n : ns) {
        String s = df.format(simplify(n));
        System.out.println("    " + n + " is " + s);
    }
}

The results are:

2.39999995546E8 is 240000000
2.39989995546E8 is 239990000
340000.007 is 340000
34.0000007 is 34
5.699999999E9 is 5700000000
235.00000002 is 235
9875.999999997 is 9876
-4334.345345 is -4334.345
2.3500000001E10 is 23500000000
0.30000007 is 0.3
-0.053999949 is -0.054

Edit I adjusted the code to use double, fixed the error with exponents < 0 and added another example. Additionally I plugged in a DecimalFormat. Note that adding more # might change some results, i.e. -0.053999949 will now show up as -0.054, with more digits it will result in -0.05399995.

OTHER TIPS

You are probably trying to ask how to store and handle currencies. Short answers are: use the BigDecimal type or create your own currency implementation with separate integer fields for the Euros and Cents (or Dollars if ou prefer :) ).

Already explained: Using BigDecimal to work with currencies

Why not use Double or Float to represent currency?

Since you ask for ideas, how about this one: make N not a number of consecutive zeroes or nines, but a threshold at which rounding a number is still close to the original number. Then go from the least significant decimal position, rounding the number one position at a time, and dividing the number you get by what you had before. Stop if the ratio exceeds your threshold. Experiment with the ratio and border conditions at which the rounding looks good to you. Use BigDecimal to avoid problems with floating point arithmetic.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top