How do qNaNs and sNaNs look experimental?
Let's first find out how to determine if we have sNaN or qNaN.
In this answer, I will use C ++ instead of C because it offers convenient std::numeric_limits::quiet_NaN and std::numeric_limits::signaling_NaN which I could not find in C conveniently.
However, I could not find a function to classify if NaN is equal to sNaN or qNaN, so let's just print the raw bytes of NaN:
main.cpp
#include <cassert> #include <cstring> #include <cmath> // nanf, isnan #include <iostream> #include <limits> // std::numeric_limits #pragma STDC FENV_ACCESS ON void print_float(float f) { std::uint32_t i; std::memcpy(&i, &f, sizeof f); std::cout << std::hex << i << std::endl; } int main() { static_assert(std::numeric_limits<float>::has_quiet_NaN, ""); static_assert(std::numeric_limits<float>::has_signaling_NaN, ""); static_assert(std::numeric_limits<float>::has_infinity, ""); // Generate them. float qnan = std::numeric_limits<float>::quiet_NaN(); float snan = std::numeric_limits<float>::signaling_NaN(); float inf = std::numeric_limits<float>::infinity(); float nan0 = std::nanf("0"); float nan1 = std::nanf("1"); float nan2 = std::nanf("2"); float div_0_0 = 0.0f / 0.0f; float sqrt_negative = std::sqrt(-1.0f); // Print their bytes. std::cout << "qnan "; print_float(qnan); std::cout << "snan "; print_float(snan); std::cout << " inf "; print_float(inf); std::cout << "-inf "; print_float(-inf); std::cout << "nan0 "; print_float(nan0); std::cout << "nan1 "; print_float(nan1); std::cout << "nan2 "; print_float(nan2); std::cout << " 0/0 "; print_float(div_0_0); std::cout << "sqrt "; print_float(sqrt_negative); // Assert if they are NaN or not. assert(std::isnan(qnan)); assert(std::isnan(snan)); assert(!std::isnan(inf)); assert(!std::isnan(-inf)); assert(std::isnan(nan0)); assert(std::isnan(nan1)); assert(std::isnan(nan2)); assert(std::isnan(div_0_0)); assert(std::isnan(sqrt_negative)); }
Compile and run:
g++ -ggdb3 -O3 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp ./main.out
output to my x86_64 computer:
qnan 7fc00000 snan 7fa00000 inf 7f800000 -inf ff800000 nan0 7fc00000 nan1 7fc00001 nan2 7fc00002 0/0 ffc00000 sqrt ffc00000
We can also run the program on aarch64 in QEMU user mode:
aarch64-linux-gnu-g++ -ggdb3 -O3 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp qemu-aarch64 -L /usr/aarch64-linux-gnu/ main.out
and this produces exactly the same conclusion, suggesting that several arches closely implement IEEE 754.
At this point, if you are not familiar with the IEEE 754 floating point structure, take a look at: What is a subnormal floating point number?
In binary, some of the values above are:
31 | | 30 23 22 0 | | | | | -----+-+------+-+---------------------+ qnan 0 11111111 10000000000000000000000 snan 0 11111111 01000000000000000000000 inf 0 11111111 00000000000000000000000 -inf 1 11111111 00000000000000000000000 -----+-+------+-+---------------------+ | | | | | | +------+ +---------------------+ | | | | vv | exponent fraction | v sign
From this experiment we see that:
qNaN and sNaN seem to be differentiated only with bit 22: 1 means quiet, and 0 means signaling
infinities are also very similar with exponents == 0xFF, but they have a fraction == 0.
For this reason, NaN must set bit 21 to 1, otherwise it would be impossible to distinguish sNaN from positive infinity!
nanf() creates several different NaNs, so there should be several possible encodings:
7fc00000 7fc00001 7fc00002
Since nan0 matches std::numeric_limits<float>::quiet_NaN() , we std::numeric_limits<float>::quiet_NaN() that they are all different quiet NaNs.
The draft C11 standard N1570 confirms that nanf() generates silent NaN, because nanf to strtod and 7.22.1.3 "strtod, strtof and strtold functions" read:
A NAN or NAN character sequence (n-char-sequence opt) is interpreted as silent NaN if supported in the return type, otherwise as part of a subject sequence that does not have the expected shape; the meaning of the n-char sequence is determined by the implementation. 293)
See also:
- How to produce NaN float in c?
- C / C ++ NaN constant (literal)?
What do qNaNs and sNaNs look like in manuals?
IEEE 754 2008 recommends (TODO mandatory or optional?):
- all with exponent == 0xFF and a fraction! = 0 is NaN
- and that the bit with the largest share distinguishes qNaN from sNaN
but it doesn't seem to say which bit is preferred to distinguish infinity from NaN.
6.2.1 "Encoding NaN in binary formats" reads:
This subclause further defines the NaN encodings as bit strings when they are the result of operations. When encoding, all NaNs have a signed bit and a set of bits necessary to identify the encoding as NaN, which determines its type (sNaN vs qNaN). The remaining bits, which are in the field of the final value and encode the payload, which may be diagnostic information (see above). 34
All binary strings of NaN bits have all bits of the offset field of exponent E set to 1 (see 3.4). The quiet bit string NaN should be encoded with the first bit (d1) of the terminating value and the field T equal to 1. The string of the signal bit NaN should be encoded with the first bit of the field of the final value and equal to 0. If the first bit is the final value and the field is 0, another bit of the final value and the field must be nonzero in order to distinguish NaN from infinity. In the preferred encoding just described, the signaling NaN should be disabled by setting d1 to 1, leaving the remaining bits of T unchanged. For binary formats, the payload is encoded in the p-2 least significant bits of the final value field and
Intel 64 and IA-32 Architecture Software Developer's Guide - Volume 1, Base Architecture - 253665-056EN September 2015 4.8.3.4 "NaNs" confirms that x86 follows IEEE 754, distinguishing between NaN and sNaN by bit with the highest proportion :
The IA-32 architecture defines two classes of NaN: quiet NaN (QNaN) and signal NaN (SNaN). QNaN is NaN with the most significant fraction bit set; SNaN is NaN with the cleared bit of the most significant fraction.
as well as the ARM Architecture Reference Guide - ARMv8, for the ARMv8-A architecture profile - DDI 0487C.a A1.4.3 "Single precision floating point format":
fraction != 0 : the value is NaN and is either calm NaN or signal NaN. Two types of NaN differ in their most significant fraction of bits, bits [22]:
bit[22] == 0 : NaN is a signal NaN. The sign bit can take any value, and the remaining fractional bits can take any value except all zeros.bit[22] == 1 : NaN is a quiet NaN. The sign bit and the remaining fractional bits can take any value.
How are qNanS and sNaN generated?
One of the main differences between qNaNs and sNaNs is that:
- qNaN is generated by the usual built-in (software or hardware) arithmetic operations with strange values
- sNaN is never generated by built-in operations, it can be explicitly added only by programmers, for example, using
std::numeric_limits::signaling_NaN
I could not find clear quotes for IEEE 754 or C11 for this, but I also can not find the built-in operation that generates sNaNs ;-)
However, Intel’s Guidelines clearly state this in 4.8.3.4 “NaNs”:
SNaNs are commonly used to intercept or call an exception handler. They must be inserted by software; that is, the processor never generates an SNaN as a result of a floating point operation.
This can be seen from our example, where both:
float div_0_0 = 0.0f / 0.0f; float sqrt_negative = std::sqrt(-1.0f);
produces exactly the same bits as std::numeric_limits<float>::quiet_NaN() .
Both of these operations are compiled into a single x86 build instruction, which generates qNaN directly in the hardware (TODO confirmation with GDB).
What do qNaNs and sNaNs do differently?
Now that we know what qNaNs and sNaNs look like and how to manipulate them, we are finally ready to try to get sNaN to do their job and blow up some programs!
So without further ado:
blow_up.cpp
#include <cassert> #include <cfenv> #include <cmath> // isnan #include <iostream> #include <limits> // std::numeric_limits #include <unistd.h> #pragma STDC FENV_ACCESS ON int main() { float snan = std::numeric_limits<float>::signaling_NaN(); float qnan = std::numeric_limits<float>::quiet_NaN(); float f; // No exceptions. assert(std::fetestexcept(FE_ALL_EXCEPT) == 0); // Still no exceptions because qNaN. f = qnan + 1.0f; assert(std::isnan(f)); if (std::fetestexcept(FE_ALL_EXCEPT) == FE_INVALID) std::cout << "FE_ALL_EXCEPT qnan + 1.0f" << std::endl; // Now we can get an exception because sNaN, but signals are disabled. f = snan + 1.0f; assert(std::isnan(f)); if (std::fetestexcept(FE_ALL_EXCEPT) == FE_INVALID) std::cout << "FE_ALL_EXCEPT snan + 1.0f" << std::endl; feclearexcept(FE_ALL_EXCEPT); // And now we enable signals and blow up with SIGFPE! >:-) feenableexcept(FE_INVALID); f = qnan + 1.0f; std::cout << "feenableexcept qnan + 1.0f" << std::endl; f = snan + 1.0f; std::cout << "feenableexcept snan + 1.0f" << std::endl; }
Compile, run and get exit status:
g++ -ggdb3 -O0 -Wall -Wextra -pthread -std=c++11 -pedantic-errors -o blow_up.out blow_up.cpp -lm -lrt ./blow_up.out echo $?
Exit:
FE_ALL_EXCEPT snan + 1.0f feenableexcept qnan + 1.0f Floating point exception (core dumped) 136
Please note that this only happens with -O0 in GCC 8.2: with -O3 GCC pre-calculates and optimizes all our sNaN operations! I'm not sure if there is a standard compatible way to prevent this.
So, we deduce from this example that:
snan + 1.0 calls FE_INVALID , but qnan + 1.0 does not
Linux only generates a signal if it is enabled using feenableexept .
This is a glibc extension, I could not find a way to do this in any standard.
When a signal occurs, this is because the CPU hardware itself throws an exception, which the Linux kernel processes and reports to the application through the signal.
As a result, bash prints a Floating point exception (core dumped) , and the output state is 136 , which corresponds to signal 136 - 128 == 8 , which corresponds to:
man 7 signal
This is SIGFPE .
Note that SIGFPE is the same signal that we get if we try to divide an integer by 0:
int main() { int i = 1 / 0; }
although for integers:
- dividing anything by zero raises the signal, because in integers there is no representation of infinity
- signal this happens by default, without the need for
feenableexcept
How to handle SIGFPE?
If you just create a handler that returns normally, this leads to an infinite loop, because after the handler returns, division happens again! This can be verified using GDB.
The only way is to use setjmp and longjmp to move to another location, as shown in: C processes the SIGFPE signal and continues execution
What are some real applications of sNaNs?
Honestly, I still do not understand the super useful use case for sNaNs, they asked about this: Is the NaN signaling useful ?
sNaNs seem especially useless because we can detect the initial invalid operations ( 0.0f/0.0f ) that qNaNs generate with feenableexcept : it seems that snan just causes errors for more operations for which qnan not called, for example ( qnan + 1.0f )
For example:
main.c
#define _GNU_SOURCE #include <fenv.h> #include <stdio.h> int main(int argc, char **argv) { (void)argv; float f0 = 0.0; if (argc == 1) { feenableexcept(FE_INVALID); } float f1 = 0.0 / f0; printf("f1 %f\n", f1); feenableexcept(FE_INVALID); float f2 = f1 + 1.0; printf("f2 %f\n", f2); }
compilation:
gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c -lm
then:
./main.out
gives:
Floating point exception (core dumped)
as well as:
./main.out 1
gives:
f1 -nan f2 -nan
See also: How to track NaN in C ++
What are signal flags and how are they manipulated?
Everything is implemented in the processor hardware.
Flags live in some register, like a bit that tells whether an exception / signal should occur.
These registers are accessible from user space from most archives.
This piece of glibc 2.29 code is actually very easy to understand!
For example, fetestexcept implemented for x86_86 in sysdeps / x86_64 / fpu / ftestexcept.c :
#include <fenv.h> int fetestexcept (int excepts) { int temp; unsigned int mxscr; /* Get current exceptions. */ __asm__ ("fnstsw %0\n" "stmxcsr %1" : "=m" (*&temp), "=m" (*&mxscr)); return (temp | mxscr) & excepts & FE_ALL_EXCEPT; } libm_hidden_def (fetestexcept)
thus, we immediately see that the instruction uses stmxcsr which means "Save the state of the MXCSR register".
And feenableexcept implemented in sysdeps / x86_64 / fpu / feenablxcpt.c :
#include <fenv.h> int feenableexcept (int excepts) { unsigned short int new_exc, old_exc; unsigned int new; excepts &= FE_ALL_EXCEPT; /* Get the current control word of the x87 FPU. */ __asm__ ("fstcw %0" : "=m" (*&new_exc)); old_exc = (~new_exc) & FE_ALL_EXCEPT; new_exc &= ~excepts; __asm__ ("fldcw %0" : : "m" (*&new_exc)); /* And now the same for the SSE MXCSR register. */ __asm__ ("stmxcsr %0" : "=m" (*&new)); /* The SSE exception masks are shifted by 7 bits. */ new &= ~(excepts << 7); __asm__ ("ldmxcsr %0" : : "m" (*&new)); return old_exc; }
What does standard C say about qNaN vs sNaN?
The draft standard C11 N1570 explicitly states that the standard does not distinguish between them in F.2.1 Infinity, signed zeros and NaN:
1 This specification does not define the behavior of signal NaNs. Usually he uses the term NaN to mean quiet NaN. The NAN and INFINITY macros and nan functions in <math.h> provide the notation for NaN in IEC 60559 and infinity.
Tested on Ubuntu 18.10, GCC 8.2. Github upstreams: