0

Say we have three int values x, y, and z, then we cast the three values from int to double and we get dx, dy, and dz. Is this formula (dx * dy) * dz == dx * (dy * dz) always true?

The generation process and my verification is as below:

int x = 0x64e73387;
int y = 0xd31cb264;
int z = 0xd22f1fcd;

double dx = (double) x;
double dy = (double) y;
double dz = (double) z;

printf("x: %i; dx: %f\n", x, dx);
printf("y: %i; dy: %f\n", y, dy);
printf("z: %i; dz: %f\n", z, dz);

double dx_y_z = (dx * dy) * dz;
double dx_z_y = (dx * dz) * dy;

printf("dx_y_z: %f; dx_z_y: %f\n", dx_y_z, dx_z_y);
 
printf("dxy based on dx_y_z: %f\n", dx_y_z / dz);
printf("dxy based on dx_z_y: %f\n", dx_z_y / dz);

return 0;

From my point of view, I know it is stupid to compare two floating-point numbers directly as they are just the approximation based on IEEE standard and are not the exact math value. But when I try to print out the values, I get the results as below and I am confused:

x: 1692873607; dx: 1692873607.000000
y: -753094044; dy: -753094044.000000
z: -768663603; dz: -768663603.000000
dx_y_z: 979963870399385375233540096.000000
dx_z_y: 979963870399385512672493568.000000
dxy based on dx_y_z: -1274893030676496640.000000
dxy based on dx_z_y: -1274893030676496640.000000

Now I have two questions:

  1. Why the values of dx_y_z and dx_z_y differ that much?
  2. Why when I try to calculte the dxy based on two different value dx_y_z and dx_z_y, I get exactly the same value?
  • 2
    Multiplication with floating point is not transitive. You will get different results depending on the order in which operators are applied. There's a very limited amount of precision available, and `n + n + n ... + n` combining `m` entries is not the same as `n * m`. – tadman Nov 06 '20 at 07:56
  • 1
    `double` has 15 decimal digits of precision, and these digits seem to be equal in this example. – vgru Nov 06 '20 at 07:58
  • 1
    The difference between the numbers are in the 16th digit.. how is that *that much*? – super Nov 06 '20 at 07:58
  • 1
    Welcome to the wild world of floating point math! If you absolutely need more precision you'll need to use an arbitrary precision math library. – tadman Nov 06 '20 at 08:00
  • @tadman, I just tried the dz_y_x, which is calculated using dz * dy * dx, but I still get the same value as dx_z_y. In this case why I still get the same value although I use a different operation order? – 气球牛仔 Nov 06 '20 at 08:07
  • You will get the same, or very close to the same values so long as you do not exceed the ability of `double` to represent your number. As soon as you step out of bounds even slightly you'll start to see artifacts. I think your second operation pulls it back into the safe zone and rounds off the differences, but in `dx_y_z` form you're outside of the limit. They're coincidentally both the same approximation. This is a lucky thing, as doing other math to get to that could result in a different outcome. – tadman Nov 06 '20 at 08:08
  • The correct results are `979963870399385486988918924` for `x*y*z` and `-1274893030676496708` for both `x*y` and `x*y*z/z`. This was done with Ruby which uses a "bigint" representation for larger values, so it does integer math correctly at any magnitude. – tadman Nov 06 '20 at 08:11
  • It's worth noting that `int * int` will not necessarily "fit" inside a `double`, that you will get rounding if they have more than 53 bits of numerical data in their *combined* values. – tadman Nov 06 '20 at 08:14
  • You should also get familiar with how compilers work. If you print out `dx_y_z / dz`, it is effectively the same as `(dx * dy) * dz / dz`. A compiler can calculate it within optimizations simply as `dx * dy`. Which may eliminate the rounding problem. Live demo: https://godbolt.org/z/746E8s. – Daniel Langr Nov 06 '20 at 09:28
  • @DanielLangr That's only because you used the `-ffast-math` option though. Without that option, it is more careful to calculate the result as the abstract machine would calculate it. – Ian Abbott Nov 06 '20 at 09:44
  • @IanAbbott Agreed. I just wanted to point out possible optimization issues. We don't know how OP compiles their code. – Daniel Langr Nov 06 '20 at 10:08

0 Answers0