by mulle_nat on 1/15/20, 12:49 PM with 159 comments
by mokus on 1/16/20, 3:03 PM
Casting the value to double ends up converting the long value 0x7fffffffffffffff to the nearest double value: 0x8000000000000000. As the -O0 version CORRECTLY reports, this does not round-trip back to the same value in the "long" type. Many other values, though not all, down to about 1/1024 of that value (1 / 2^(63-53)) will also fail to round-trip for similar reasons.
Unless my coffee-deficient brain is missing something at the moment, it should be the case that any integer with 53 bits or fewer between the first and last 1 bit (inclusive) will roundtrip cleanly. Any other integer will not.
Edit: fixed a typo above, and coded up the idea I expected to work and ran it through quickcheck for a few min, and this version seems to be correct ('int' return rather than bool is just because haskell's ffi doesn't natively support C99 bool):
#include <limits.h>
int fits(long x) {
if (x == LONG_MIN) return 0;
unsigned long ux = x < 0 ? -x : x;
while (ux > 0x1fffffffffffffUL && !(ux & 1)) {
ux /= 2;
}
return ux <= 0x1fffffffffffffUL;
}
by 0xff00ffee on 1/16/20, 4:44 PM
When you work with floating point, you need to remember you work with a tolerance to epsilon for comparisons because you are rounding to 1/n^2 precision and different floating point units perform the conversion in different ways.
You must abandon the idea of '==' for floats.
This is why his code is unpredictable, because you cannot guarantee the conversion of any integer to and from float is the same number. Period. The LSBs of the mantissa can and do change, which is why we mask to a precision or use signal-to-noise comparisons when evaluation bit drift between FP computations.
He has the first part correct, < and > are your friend with FP. But to get past the '==' hurdle, he needs to define his tolerance, the code should be something like:
if (fabs(f1 - f2) > TOLERANCE) ... fits = true.
I was irked by his arrogance when he asks, "Intel CPUs have a history of bugs. Did I hit one of those?" First, learn about floating point, then, work on an FPUnit team for 10 years, and even then, don't assume you're smarter than a team of floating point architects, you're not.
by hannob on 1/16/20, 12:25 PM
clang test.c -O0 -fsanitize=undefined
./a.out
[...]
test.c:17:12: runtime error: 9.22337e+18 is outside the range of representable values of type 'long'
Interestingly gcc doesn't throw that warning.
by ThreeFx on 1/16/20, 11:59 AM
This should be fairly obvious with knowledge about how floating point numbers are represented internally IMO.
Edit: Be more precise about what can be represented.
by dfranke on 1/16/20, 2:51 PM
Edit: check for me whether just calling lrint(x) works. The manpage doesn't specify that lrint() will set FE_INEXACT, but it seems weird to me that it wouldn't.
by g82918 on 1/16/20, 4:21 PM
by pjc50 on 1/16/20, 1:10 PM
I don't know if this is supposed to be a joke or part of the setup for an explanatory post about undefined behaviour, but that list is in exactly the wrong order.
by heftig on 1/16/20, 3:42 PM
What you're seeing is not excess precision due to wide registers but excess precision due to optimization and constant propagation, which means GCC calculates a fast path for (argc == 1) that doesn't round correctly and ends up with "it fits".
Interestingly it does optimize to the correct "doesn't fit" with -mfpmath=387 -fexcess-precision=standard, so I guess this is a bug in how GCC treats SSE math. The sanitizer (-fsanitize=float-cast-overflow) also notices the problem.
by yuriko on 1/16/20, 12:33 PM
by mojuba on 1/16/20, 12:57 PM
if( ! fits)
Why this (constently) terrible formatting though? Never seen anyone using this style.by mulle_nat on 1/17/20, 1:07 AM
#include <math.h>
#include <fenv.h>
int fits_long( double d)
{
long l_val;
double d_val;
// may be needed ?
// #pragma STDC FENV_ACCESS ON
feclearexcept( FE_INVALID);
l_val = lrint( d);
d_val = (double) l_val;
if( fetestexcept( FE_INVALID))
return( 0);
return( d_val == d);
}
The article explains it in more detail. Thanks for the help.by NullPrefix on 1/16/20, 3:57 PM
by ginko on 1/16/20, 12:20 PM
I'd say the cleanest would be to decode exponent and mantissa, check if the exponent is within the 64-bit limit of long, then check if there's any bits set below the decimal point. (+plus some extra care for two's complement negative numbers)
The problem with this is of course that this would be platform dependent.
by apta on 1/16/20, 4:19 PM
by Aardwolf on 1/16/20, 3:50 PM
Is there a way to get the largest double smaller or equal than some positive integer?
by correct_horse on 1/16/20, 5:40 PM
I really dislike the arrogant programmer trope. Can we all stop?
by CGamesPlay on 1/16/20, 12:51 PM
by adammunich on 1/16/20, 9:02 PM
by syockit on 1/16/20, 1:41 PM