New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
demoting floating float values to unrepresentable types is undefined behavior #75554
Comments
According to C99, "When a finite value of real floating type is converted to an integer type other than _Bool, the fractional part is discarded (i.e., the value is truncated toward zero). If the value of the integral part cannot be represented by the integer type,the behavior is undefined." So, e.g., (long)x, where x is a double is in general undefined behavior without a range check. We have several cases in CPython where we need to fix this. |
There's a (to my mind) unfortunate change in behaviour here. Under normal IEEE 754 rules, some C double values larger than FLT_MAX still round to FLT_MAX under conversion to float. Python 3.6: >>> import struct
>>> x = 3.40282356e38
>>> struct.pack("<f", x)
b'\xff\xff\x7f\x7f' Following the changes in this PR: >>> import struct
>>> x = 3.40282356e38
>>> struct.pack("<f", x)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OverflowError: float too large to pack with f format The original behaviour is correct with respect to IEEE 754; the new behaviour is not. I think this is a case where the cure is worse than the disease. |
I agree that's bad. Do you know a way to get the IEEE 754 rounding behavior without invoking C undefined behavior? |
One option is to hard-code the actual boundary, which is 2**128 * (1 - 2**-25) (as opposed to FLT_MAX, which is 2**128 * (1 - 2**-24)): values equal to or larger than 2**128 * (1 - 2**-25) in absolute value should raise. But that means assuming IEEE 754 and round-ties-to-even, which isn't an outrageous assumption but does make the solution feel a bit fragile. An alternative would be to scale values in the range (FLT_MAX, 2.0 * FLT_MAX] by 0.5 before doing the conversion, something like this: if (fabs(x) > FLT_MAX && !Py_IS_INFINITY(x)) {
double half_x = 0.5 * x;
if (half_x > FLT_MAX) {
goto Overflow;
}
float half_y = (float)half_x;
if (half_y > 0.5 * FLT_MAX) {
goto Overflow;
}
} |
I'm going to undo the changes to getargs.c and floatobject.c. I think the pytime.c change is still correct because the doubles are explicitly rounded before conversion (and the old code checked for error > 1.0). It's hard to win here I think. The clang undefined behavior sanitizer uses FLT_MAX to make its determination: So to satisfy it and preserve the old behavior, we would presumably have to implement the rounding ourselves, which doesn't seem like fun. Annex F of C99, which defines the requirements for C implementations using IEE754, says, "the conversions for floating types provide the IEC 60559 conversions between floating-point precisions.". Those are, of course, fully defined. It seems like the undefined behavior sanitizer is being overzealous when the target supports IEEE754. |
Agreed.
Also agreed. |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: