2

So:

//aValue = 4.45
//bValue = 4.35
//maxValueDiff = 0.1
//The absolute value of a - b = 0.1

if( Math.abs(aValue - bValue) <= maxValueDiff ) return true;
logger.info("valueDiffCheck a:" + aValue +" b:"+ bValue + " e1:" + maxValueDiff);

Why do I see this in my logs:

valueDiffCheck a:4.45 b:4.35 e1:0.1

Suspect this is because Double is a double pain with it's inaccuracies but if that were the case shouldn't I see that? Like it should print that 0.1 is actually 0.10000000111 or something? ...or is there something pesky about the printing format?

The target code is high performance with a LOT of data so we don't want to use BigDecimal. What's the next best thing to ensure that 0.44 is 0.44?

OK let me state unequivocally that we only need two decimal places. The accuracy involved in using BigDecimal is major overkill.

user447607
  • 5,149
  • 13
  • 33
  • 55
  • You would have to use a larger decimal to compare to if you wanted higher precision. Subtraction and addition aren't problems though. You should be more concerned with division if you are worrying about precision issues. – Luminous Oct 17 '14 at 17:25
  • @Luminous, "Don't know what you mean by "subtraction and addtion aren't problems." If you write `c = a + b` where a, b, and c are doubles; then it is possible that c is not equal to the real number c' that is the actual sum of a and b. The difference between c and c' can be as much as 1/2 of one ULP. – Solomon Slow Oct 17 '14 at 17:31
  • actually, it's printing correctly. What you should print is Math.abs(aValue - bValue) – Leo Oct 17 '14 at 17:34
  • Wrote a short unit test and it's the math that causes the inaccuracy. All I need are the two decimal places. The result turns out to be something silly like 0.1000000000000123 – user447607 Oct 17 '14 at 17:36
  • Do not use floating points at all. Instead multiply you input with 1.000.000 and display output appropriately. Six digits as remainder should be enough. – Hannes Oct 17 '14 at 17:38
  • 2
    Please read [What Every Computer Scientist Should Know About Floating-Point Arithmetic](http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html). This question comes up several times per week on SO. The simple answer is that it is not possible without using exact decimal arithmetic (i.e. `BigDecimal`). – Jim Garrison Oct 17 '14 at 17:38
  • @Hannes Enough for what? The OP did not tell us what he/she is trying to accomplish. All we've got is one example. What do those numbers mean? If the OP changed the 0.1 to 0.1000000001, that would make the test pass. Why is 0.1000000001 not acceptable? Without more information, we don't know. – Solomon Slow Oct 17 '14 at 17:46
  • Since all we ever do is simple math with values that have only 2 decimals, the solution we used in the end was to multiply by 1000 cast to integer and then do the comparison. It's not accurate but if we needed accuracy we wouldn't be using only two decimal places. ...and no this isn't money. It's the rough length or width of a rock. – user447607 Oct 17 '14 at 21:09
  • @user447607 Multiplying by 1000 and casting to integer can result in pretty bad rounding errors... `4.3999999` -> `4399.99999` -> `4399` -> `4.399`; you need to have an `ERROR` factor anyway. – durron597 Oct 20 '14 at 13:25
  • Yes... but we don't care for this application. – user447607 Oct 21 '14 at 21:13

3 Answers3

3

I solve this problem by using an error margin in places where it might come up. The following is a very simple way - but not necessarily the best way (you may want it more centralized, configurable, etc.) - to modify your code to do this:

private static final double ERROR = 1e-7;

// SNIP

if( Math.abs(aValue - bValue) <= maxValueDiff + ERROR) return true;
logger.info("valueDiffCheck a:" + aValue +" b:"+ bValue + " e1:" + maxValueDiff);
durron597
  • 31,968
  • 17
  • 99
  • 158
  • Nice one ... now find all the places to insert this in ;) – Drejc Oct 17 '14 at 17:45
  • @Drejc The best thing to do is have a method like "DoubleChecker" - you can even use a custom [Hamcrest](https://code.google.com/p/hamcrest/) matcher - but all of that is more complicated than the scope of this question. – durron597 Oct 17 '14 at 17:51
2

If you want to be exact, I don't see any way around using BigDecimal.

But depending what you do with this numbers you can always round using Math.round to "ensure" you have the "correct" decimals set.

Round a double to 2 decimal places

Community
  • 1
  • 1
Drejc
  • 14,196
  • 16
  • 71
  • 106
  • The question as stated was about how to avoid BigDecimal. The provided solution uses BigDecimal. We only need 2 decimal places. – user447607 Oct 17 '14 at 17:38
  • The solution is to use Math.round if you read the answer ... and the answers in the linked question. – Drejc Oct 17 '14 at 17:44
0

You can always consider using your own decimal class that uses long to store the value and a decimal placeholder.

public Class MyDecimal
{
    private long value = 0;
    private long decimalPlace = 0;

    ...
}

This means you can make 0.44 be doing something like:

MyDecimal decimal = new MyDecimal(44, 2);

As mentioned in the other answer though, BigDecimal will be your best bet to ensure accuracy.

sfedak
  • 676
  • 6
  • 20