3 ms·
Not quite. nextafterf() is trying to return the smallest float after 0.0 in the direction of infinity (infinity is still valid even when the FPU is configured t
by moyix 4y ago
Not quite. nextafterf() is trying to return the smallest float after 0.0 in the direction of infinity (infinity is still valid even when the FPU is configured to flush denormal numbers to 0; infinity isn’t a denormal number). But since the smallest float after zero is denormal, when a shared library built with -ffast-math is loaded (which sets the FPU’s FTZ and DAZ bit) denormal numbers get forced to zero, and so nextafterf returns 0.0 and the comparison becomes (x >= 0.0), which means that x=0.0 passes the check and ceilf returns 0.0 rather than 1.0.
- ridiculous_fish 4y ago`nextafterf` does not in fact return 0. FTZ flushes denormals to zero when they are the result of an FP calculation, but `nextafter` is implemented using integer arithmetic, because it is much easier: simply add 1 as an integer, with special handling for Inf/NaN/-0. It's only later that it gets treated as zero by DAZ: in the FP comparison, and then the call to ceil(), both of which use the FPU. edit: confirmed, you can see it in action on this gist. When compiled with -ffast-math then the denormal does not compare greater than zero (it is flushed to zero by the comparison), but its bit pattern is integer value 1, which is nextafter(0). https://gist.github.com/ridiculousfish/bfa7ed6b1d76c114e19b379e1b9be16f https://gist.github.com/ridiculousfish/bfa7ed6b1d76c114e19b3...
- moyix 4y agoI don't think this is correct. The return value of nextafterf is a float, and the ABI requires those to be returned in xmm0. So although nextafterf is using integers internally, the flush to zero happens inside of nextafterf when it moves eax into xmm0; even though eax is 1, xmm0 becomes 0.0. You can see that by stepping through it in gdb: https://gist.github.com/moyix/846afb7fcec7c618a6ef52d93b4ca7b9 https://gist.github.com/moyix/846afb7fcec7c618a6ef52d93b4ca7...
- ridiculous_fish 4y agoDenormals are not flushed to zero merely by `mov xmm,rax` - they can't be, since xmm is also used for integer arithmetic, and the CPU doesn't "know" whether it's an FP or integer value at the point of the mov. Your gist demonstrates this: notice in the bottom right that xmm0 contains `v2_int64 = {0x1, 0x0}` so the bottom half containing the result is nonzero. (The top half is zero as it's unused). The "info" command rounds the value to 0 for compactness, but if you run `p $xmm0.v2_double` you will also see it is nonzero when interpreted as a double (I get 4.94e-324). A fun aside: that `mulss` instruction you stopped at is just there to raise an underflow FP exception; its result is discarded. https://github.com/bminor/glibc/blob/595c22ecd8e87a27fd19270ed30fdbae9ad25426/math/s_nextafter.c#L81 https://github.com/bminor/glibc/blob/595c22ecd8e87a27fd19270...
- moyix 4y agoAh you're right! I was misled by the `v4_float = {0x0, 0x0, 0x0, 0x0}` portion.
- gabcoh 4y agoWould it be fair to call this a bug in nextafterf? If this FPU flag is enabled it would seem like returning a denormal here is incorrect as it isn’t representable in this cpu in this configuration.
- stephencanon 4y agoNone of the relevant standards say anything about how math functions are supposed to behave in the non-standard DAZ/FTZ modes (because they're non-standard). The second you set the bit, you step off the path of being able to complain about any behavior not documented by the library.
- an1sotropy 4y agoRight - it's a bit like saying "some more undefined behavior please!" and then being surprised at being surprised.
- stephencanon 4y agoIdeally, IEEE 754 would define behavior when subnormals are flushed, and would define nextUp(0) to be the smallest representable value (i.e. min normal for FTZ/DAZ modes). Unfortunately, the committee has never been willing to define behavior for non-standard arithmetic, despite multiple people (Ian Ollmann has suggested it a few times, and others have as well) requesting it over the years.
- jcranmer 4y agoI agree that IEEE 754 should define how FTZ/DAZ should work, given that it's a pretty common extension to floating-point processors, and language committees are unlikely to lift a finger to do anything floating-point related that isn't from IEEE 754.