4 ms·
The title is actually wrong - the -O0 version is correct, the -O3 version is not (despite giving the output the author expected). Casting the value to double e
by mokus 7y ago
The title is actually wrong - the -O0 version is correct, the -O3 version is not (despite giving the output the author expected).
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;
}
- deleted 7y ago[deleted]
- gok 7y agoOP wants the opposite; they're converting from double to long.
- Someone 7y ago“Unless my coffee-deficient brain is missing something at the moment” Add enough zeroes, and you’ll run out of exponent range.
- raverbashing 7y ago> converting the long value 0x7fffffffffffffff to the nearest double value: 0x8000000000000000 From what I understand from the spec it should be the nearest in value, no? Not the nearest in memory representation.
- eMSF 7y agoWho said anything about memory representations? The language spec (or at least a summary of it) is linked in the article, and it is pretty loose: nearest higher or nearest lower, chosen by the implementation (regardless of which is nearer). IEEE double-precision float cannot store LONG_MAX (which is 2^63-1 with 8-byte longs) precisely, so it gets converted to 2^63; which you cannot cast back to a long, because it doesn't fit (resulting in undefined behaviour).
- OskarS 7y agoSo the problem here is that in this line: if( value < (double) LONG_MIN || value > (double) LONG_MAX) return( 0); The cast of LONG_MAX to double rounds upwards (which is allowed by the standard, which says that rounding direction is "implementation defined"), so that "value > (double) LONG_MAX" is false, right? Even though the mathematical value of "value" is larger than LONG_MAX? Which then leads to this line: l_val = (long) value; Where value is cast to a long despite being outside of the range of longs, thus causing undefined behavior. So to be clear, BOTH of the -O0 and -O3 versions are correct, since both invoke undefined behaviour. When -O0 and -O3 give different results, either at least one of them is incorrect and you've stumbled on a compiler bug, or both of them are correct and you're invoking UB (the far more common situation). EDIT: no, I think I misunderstood it: it's not that value is larger than (double) LONG_MAX, it IS (double)LONG_MAX, so of course "value > (double)LONG_MAX" is false. The problem is that the "(long)((double)LONG_MAX))" is undefined behaviour on implementations that round (double)LONG_MAX upwards instead of downwards. Which is allowed by the standard. Ok, cool :)
- Joker_vD 7y agoHuh. So, how does one check that a cast from double to long would succeed in C? Just go and do lround()+errno check?
- ekimekim 7y agoI think the most correct way would be to explicitly set the rounding mode to down or towards-zero, then calculate largest_double_that_fits = (double) LONG_MAX, then check value <= largest_double_that_fits. Since largest_double_that_fits is guarenteed to be <= LONG_MAX, and value <= largest_double_that_fits, it follows that value <= LONG_MAX.
- ambrop7 7y agoThis is not right. The current rounding mode is not guaranteed to apply to integer to floating point conversions. Quoting C99, section 6.3.1.4, paragraph 2: "When a value of integer type is converted to a real floating type, if the value being converted can be represented exactly in the new type, it is unchanged. If the value being converted is in the range of values that can be represented but cannot be represented exactly, the result is either the nearest higher or nearest lower representable value, chosen in an implementation-defined manner. If the value being converted is outside the range of values that can be represented, the behavior is undefined." See it says implementation-defined manner, not according to the current rounding mode. Test case: #pragma STDC FENV_ACCESS ON #include <stdio.h> #include <stdint.h> #include <fenv.h> int main() { fesetround(FE_DOWNWARD); printf("%f\n", (double)UINT64_MAX); fesetround(FE_UPWARD); printf("%f\n", (double)UINT64_MAX); return 0; } $ gcc -std=c99 -frounding-math x.c -lm $ ./a.out 18446744073709551616.000000 18446744073709551616.000000 In both cases it rounded up.