0

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.

andbi
  • 4,426
  • 5
  • 45
  • 70
  • 2
    There doesn't seem to be an actual question here. – Oliver Charlesworth Apr 26 '14 at 16:11
  • @OliCharlesworth, and what prevents it to be the question? – andbi Apr 26 '14 at 16:12
  • You need a more accurate definition of your question. Try to define the general rounding rule (according to whatever requirements you have). Then, if you have any problems implementing it, publish it here and ask for help. Your question as it is now, is extremely vague. – barak manos Apr 26 '14 at 16:14
  • Ok, got it, I just thought it was kinda obvious and well-known issue with a known solution. I'll rewrite the question. – andbi Apr 26 '14 at 16:17
  • what does it have to do with "inaccuracy", whatever you mean by it? – Denis Tulskiy Apr 26 '14 at 16:17
  • Why is `239999995.546` worse than `239989995.546`? But maybe something like this question helps you: http://stackoverflow.com/questions/7906996/algorithm-to-round-to-the-next-order-of-magnitude-in-r – Sebastian Höffner Apr 26 '14 at 16:45
  • 1
    And I just realize your rounding rules are not consistent: Why does `240000000.0` keep a `.0` while `340000` and `34` do not? Also the scientific notation provided by Formatter `%e` might help you, it gives `2.400000e+08` for `239999995.546` – Sebastian Höffner Apr 26 '14 at 16:56
  • @SebastianHöffner, I've updated the question, again)) As for inconsistency - forget about `.0`, it can be omitted, it's just the way of showing that it still the double. – andbi Apr 26 '14 at 17:25
  • After your second edit I have to admit that actually the answer suggesting `BigDecimal` seems to be exactly what you need. The problem you face there has to do with floating point precision, it's nothing about formatting results or anything. Try `1000.0 * 0.123 / 0.123` and you will not have the problem with getting `999.99999` as a result. However, you say you have the code done but maybe someone could do it better - how about you provide us the code and we can look if we can improve it? – Sebastian Höffner Apr 26 '14 at 17:26
  • My solution actually also seems to work fine with your example: `System.out.println(simplify(d*mult/mult));` prints `1000`. – Sebastian Höffner Apr 26 '14 at 17:34
  • I feel I still need to clarify my points here a little. My solution just prints as you requested. But to solve the problem in a better way is to follow the answer given by @Aleksander because it tackles the root course of your problem. Nobody ever said you should use `BigDecimal` only for currencies - and your examples "percentage, big numbers, small fractions" etc. fit perfectly into applications for BigDecimals. – Sebastian Höffner Apr 26 '14 at 17:38
  • @SebastianHöffner, sure i'd use the BigDecimal, but I never told that it's me who does the math, I receive these numbers via a web service which out of my control(( – andbi Apr 26 '14 at 17:45
  • Okay, I think your calculation there was misleading, it implies somehow that you are doing the calculations and have to deal with problematic results. – Sebastian Höffner Apr 26 '14 at 17:49
  • possible duplicate of [How to round a number to n decimal places in Java](http://stackoverflow.com/questions/153724/how-to-round-a-number-to-n-decimal-places-in-java) – mmmmmm Apr 26 '14 at 20:40

3 Answers3

2

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?

Community
  • 1
  • 1
Aleksandar Stojadinovic
  • 4,851
  • 1
  • 34
  • 56
2

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.

Sebastian Höffner
  • 1,864
  • 2
  • 26
  • 37
  • Looks interesting. But what happened to `-4334.345345`, why is it changed? – andbi Apr 26 '14 at 17:31
  • The scientific notation is `-4.334345e+03`, the code grabs divides it into `-4.334345` and `3` and calculates `4.334345 * 10^3 = 4334.345` which is then cast to a `long`, hence the decimal digits get thrown away. Change the return type to `double` and you will get better results, though than you have to fight with lots of 0s after the period. edit: and for that there are already answers: http://stackoverflow.com/questions/11284938/remove-trailing-zeros-from-double – Sebastian Höffner Apr 26 '14 at 17:42
  • Ok, i see. Just tested, it's not working with `0.30000007` for example, but I think I get your idea, can fix it on my own. Thank you for your patience)) – andbi Apr 26 '14 at 17:53
  • I checked it, try `+1` instead of `+2` for the exponent, I didn't think about negative exponents. I'll update my answer, also to reflect doubles. – Sebastian Höffner Apr 26 '14 at 20:25
1

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.

Denis Tulskiy
  • 19,012
  • 6
  • 50
  • 68