8

I have the following double calculation in Java:

double C1 = some value;    // It's always an integer
double C2 = some value;    // It's always integer, and C2 >= C1
double S = some value;     // It's always 0 < S <= 1
double carry = some value; // It's always 0 <= carry < 1

double rate = ((C1/C2)/S);
double C3 = Math.floor( (rate * S * C2) + carry );

Now, if we did not lose precision, C3 == C1 would be true. But since we lose precision, will C3 and C1 still always be equal?

If they would not always be equal, if I have C1, C2, S, and C3, how could I modify the rate to make sure after calculation C3 would be equal to C1?

Note: unfortunately using BigDecimal is not an option.

user1819676
  • 369
  • 1
  • 12
  • 1
    if you are interested in keeping the precision then do use a `BigDecimal`. – SomeJavaGuy Apr 15 '16 at 06:36
  • unfortunately using BigDecimal is not an option, as the rate is already stored after the precision is lost, and I can't do anything about it. – user1819676 Apr 15 '16 at 06:38
  • 7
    @KevinEsche BigDecimal only prevents loss of precision if the numbers are all representable as terminating decimal fractions. Consider the case of C1=1, C2=1, S=0.3 – Patricia Shanahan Apr 15 '16 at 06:40
  • @PatriciaShanahan thanks I just wanted to point that out. People think that BigDecimal is some kind of magic bullet but it suffers from the same problems as double, except that it uses decimal rather than binary fractions. – Erwin Bolwidt Apr 15 '16 at 06:42
  • Possible duplicate of [Best way to represent a fraction in Java?](http://stackoverflow.com/questions/474535/best-way-to-represent-a-fraction-in-java) – Chris Martin Apr 15 '16 at 07:24
  • 1
    a numerical function f(x) has a condition number (input error amplification) of xf'(x)/f(x), see https://en.wikipedia.org/wiki/Condition_number – Exceptyon Apr 15 '16 at 07:36
  • Is it possible to avoid using `floor`, or to add a small epsilon to its argument? – Patricia Shanahan Apr 15 '16 at 10:09
  • floor cannot be avoided, but rate that we pass to the function could be modified (let's call it `newRate`), in order to guarantee `Math.floor( (newRate * S * C2) + carry )` would output `C1` – user1819676 Apr 15 '16 at 14:03
  • It's a constant less that 1 number that we cannot change. It will always be added. – user1819676 Apr 15 '16 at 14:22
  • `carry` may be intended to be the fudge factor you need to make the `floor` come out right. Does it ever fail with an actual set of values, rather than your test data? – Patricia Shanahan Apr 15 '16 at 14:30
  • The purpose of the carry is to move forward the carry from the previous computation (so we don't keep losing data). As it's a constant carried forward from a totally different computation, I doubt it helps here. No I can't make this fail right now, but I'm wondering if there is a possible value that could cause this fail. – user1819676 Apr 15 '16 at 15:39

2 Answers2

3

Generally, we don't compare two double values directly through ==. Instead, we measure the difference between them. e.g. Math.abs(C3 - C1) < 1e-6.

Kevin Cruijssen
  • 9,153
  • 9
  • 61
  • 135
Hongliang
  • 76
  • 4
  • Yes, if it was a matter of comparing doubles. But the function that performs Math.floor returns an integer. Look at it this way. `S` and `C2` are stored in db, and we have that function that in future will be used to calculate C3. Now we want to store `rate` or some modification of `rate` to db to guarantee that function will generate `C3 == C1` – user1819676 Apr 15 '16 at 07:34
3

Not so.

Floating point is an approximation. The next double, bit 1 added to the mantissa can cause a quite large gap for large exponents between those two doubles.

S approximating 0 will cause huge doubles, that have a large gap between them. That will no longer be correctable, say with that simple rounding. Even if the small S does not cause the division to exceed the double range with INFINITY.

One can easily be of by 1000.

Joop Eggen
  • 107,315
  • 7
  • 83
  • 138