0

Take this simple function for example.

   int checkIfTriangleIsValid(double a, double b, double c) {
      //fix the precision problem
      int c1, c2, c3;
      c1 = a+b>c ? 0 : 1;
      c2 = b+c>a ? 0 : 1;
      c3 = c+a>b ? 0 : 1;
      if(c1 == 0 && c2 == 0 && c3 == 0)
        return 0;
      else {
        printf("%d, %d, %d\n",c1, c2, c3);
        return 1;
      }
   } 

I place for a = 1.923, b = 59.240, c = 61.163

Now for some reason when I check for the condition in c1 it should give me 1, but instead, it gives me 0. I tried to do a printf with %.30f and found that the values later changes.

How can I fix this problem?

EDIT: I checked the other questions that are similar to mine but they don't even have a double.

Roy Scheffers
  • 3,832
  • 11
  • 31
  • 36
Mark
  • 9
  • 6
  • value changes *how*? after many decimals? 64 vs 80-bit floats – Antti Haapala -- Слава Україні Oct 27 '18 at 19:43
  • example of .30f% for a+b = 61.163000000000003808509063674137, c= 61.162999999999996703081706073135 – Mark Oct 27 '18 at 19:49
  • See [what every computer scientist should know about floating-point arithmetic](http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html) and [is floating point math broken](https://stackoverflow.com/questions/588004/is-floating-point-math-broken). – user3386109 Oct 27 '18 at 19:50
  • I am using c99, not sure about 64vs 80 – Mark Oct 27 '18 at 19:50
  • it looks like 64 vs 80. If kept in register it can retain 80 bits of precision then on stack it will be cut to 64. – Antti Haapala -- Слава Україні Oct 27 '18 at 19:51
  • Possible duplicate of [Improve INSERT-per-second performance of SQLite?](https://stackoverflow.com/questions/1711631/improve-insert-per-second-performance-of-sqlite) – KamilCuk Oct 27 '18 at 19:53
  • Hmm though I guess, from the answers, this is not the case... – Antti Haapala -- Слава Україні Oct 27 '18 at 19:54
  • 1
  • If you did further investigation or test code, include those results (and the code) rather then make us reproduce your test. Even without issues of converting real decimal values to _binary floating point_ and back, why would you expect `c1==1` when `a + b` is equal to, not greater then `c`? The result could be zero or 1. `double` has precision to 20 significant figures decimal - printing to 30 decimal places has no meaning. Better perhaps to do `(a+b) - c < EPSILON`, when `EPSILON` is some arbitrarily small error limit, such that you will regard `a + b` to be equal to `c`. – Clifford Oct 27 '18 at 19:58
  • @Clifford: Printing `double` values to 30 decimal places does have meaning. Floating-point arithmetic is well specified (in general; C implementations’ adherence to good principles is spotty). A skilled practitioner can make use of the full values of `double` objects. – Eric Postpischil Oct 27 '18 at 20:10
  • 1
    @Clifford: Recommending comparing with a tolerance in this case is misguided. The code is obviously intended to make a certain distinction, and there is no information about whether a false positive or a false negative is preferable, or even whether either is acceptable at all. There is no basis for expecting a test that allows a false positive for `a + b < c` is appropriate. – Eric Postpischil Oct 27 '18 at 20:12
  • @EricPostpischil - agreed. – Clifford Oct 27 '18 at 20:15
  • I think however determining something like an epsilon that is universal for all the datatypes that I could add is essential, but I am not sure what the value of it should be. – Mark Oct 27 '18 at 20:22
  • 1
    @mark I assert that an "epsilon" application is not certainly the right approach nor needed as [answered below](https://stackoverflow.com/a/53025722/2410359). Without further info on how the result of `checkIfTriangleIsValid()` steers code, an "epsilon" is [misguided](https://stackoverflow.com/questions/53025480/how-to-check-for-floating-point-precision-in-double/53025722#comment92953547_53025480). – chux - Reinstate Monica Oct 27 '18 at 21:10

2 Answers2

1

Likely your C implementation uses the IEEE-754 basic 64-bit binary floating-point format for double. When 1.923, 59.240, and 61.163 are properly converted to the nearest values representable in double, the results are exactly:

  • 1.9230000000000000426325641456060111522674560546875,
  • 59.24000000000000198951966012828052043914794921875, and
  • 61.1629999999999967030817060731351375579833984375.

As you can see, the first two of these sum to more than the third. This means that, by the time you assign these values to double objects, they have already been altered in a way that changes their relationship. No subsequent calculations can repair this, because the original information is gone.

Since no solution after conversion to double can work, you need a solution that operates before or instead of conversion to double. If you want to compute exactly, or more precisely, with the values 1.923, 59.240, and 61.163, you may need to write your own decimal arithmetic code or find some other code that supports decimal arithmetic. If you only want to work with numbers with three decimal places, then a possible solution is to write some code that reads input such as “59.240” and returns it in an integer object scaled by 1000, so that 59240 is returned. The resulting values could then easily be tested for the triangle inequality.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
1

when I check for the condition in c1 it should give me 1, but instead it gives me 0
How can I fix this problem?

Change your expectations.

A typical double can represent exactly about 264 different values. 1.923, 59.240, 61.163 are typically not in that set as double is usually encoded in a binary way. e.g. binary64.

When a,b,c are assigned 1.923, 59.240, 61.163, they get values more like the below which are the closet double.

a      1.923000000000000042632564145606...
b     59.240000000000001989519660128281...
c     61.162999999999996703081706073135...

In my case, the a, and b both received a slightly higher value than the decimal code form, while c received a slightly lower one.

When adding a+b, the sum was rounded up, further away from c.

printf("a+b %35.30f\n", a+b);
a+b   61.163000000000003808509063674137

a + b > c was true, as well as other compares and OP's
checkIfTriangleIsValid(1.923, 59.240, 61.163) should return valid (0) as it is really more like checkIfTriangleIsValid(1.9230000000000000426..., 59.24000000000000198..., 61.16299999999999670...)


Adding a+b is further complicated in that the addition may occur using double or long double math. Research FLT_EVAL_METHOD for details. Rounding mode also can affect the final sum.

#include <float.h>
printf("FLT_EVAL_METHOD %d\n", FLT_EVAL_METHOD);

As to an alternative triangle check, subtract the largest 2 values and then compare against the smallest.

a > (c-b) can preserve significantly more precision than (a+b) > c.

// Assume a,b,c >= 0
int checkIfTriangleIsValid_2(double a, double b, double c) {
  // Sort so `c` is largest, then b, a.
  if (c < b) {
    double t = b; b = c; c = t;
  }
  if (c < a) {
    double t = a; a = c; c = t;
  }
  if (a > b) {
    double t = b; b = a; a = t;
  }
  // So far, no loss of precision is expected due to compares/swaps.
  // Only now need to check a + b >= c for valid triangle

  // To preserve precision, subtract from `c` the value closest to it (`b`).
  return a > (c-b);
}

I will review more later as time permits. This approach significant helps for a precise answer - yet need to assess more edge cases. It reports a valid triangle checkIfTriangleIsValid_2(1.923, 59.240, 61.163)).

FLT_EVAL_METHOD, rounding mode and double encoding can result in different answers on other platforms.


Notes:
It appears a checkIfTriangleIsValid() returning 0 means valid triangle.
It also appears when the triangle has 0 area, the expected result is 1 or invalid.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256