The usefulness of NaN signaling?

I recently read a little about IEEE 754 and x87 architecture. I thought of using NaN as the “missing value” in some numerical computation code I'm working on, and I was hoping that using the signal NaN would allow me to catch the floating point exception in cases where I don't want to execute the “missing values” . Conversely, I would use quiet NaN to allow the “missing value” to propagate through computation. However, signal NaNs do not work, as I thought they would be based on the (very limited) documentation that exists on them.

Here is a summary of what I know (all using x87 and VC ++):

  • _EM_INVALID (IEEE exception exception) controls x87 behavior when meeting with NaNs
  • If _EM_INVALID is masked (exception thrown), an exception is not thrown, and operations can return a quiet NaN. The operation associated with signaling NaN will not throw an exception, but will be converted to a quiet NaN.
  • If _EM_INVALID is open (exception is included), an invalid operation (for example, sqrt (-1)) causes an invalid exception to be rejected.
  • x87 never generates a NaN alarm.
  • If _EM_INVALID is exposed, any use of the NaN signaling (even initializing the variable with it) will result in the rejection of an invalid exception.

The standard library provides a way to access NaN values:

std::numeric_limits<double>::signaling_NaN(); 

and

 std::numeric_limits<double>::quiet_NaN(); 

The problem is that I do not see any benefit to signaling NaN. If _EM_INVALID is masked, it behaves exactly like silent NaN. Since no NaN is comparable to any other NaNs, there is no logical difference.

If _EM_INVALID is not masked (an exception is included), then you cannot even initialize a variable with a signal NaN: double dVal = std::numeric_limits<double>::signaling_NaN(); , because it throws an exception (the NaN signaling value is loaded into the x87 register to save it to the memory address).

You might think the following, like me:

  • Mask _EM_INVALID.
  • Initialize the variable with the NaN signal.
  • Unmask_EM_INVALID.

However, step 2 causes the NaN signaling to be converted to a quiet NaN, so its subsequent use will not raise an exception! So WTF ?!

Is there any utility or purpose for signaling NaN? I understand that one of the initial intentions was to initialize the memory with it so that you could catch the value of a unified floating point value.

Can someone tell me if I missed something?




EDIT:

To illustrate what I was hoping to do, here is an example:

Consider performing mathematical operations on a data vector (doubles). For some operations, I want to allow the vector to contain a “missing value” (pretend that this corresponds to a column in a spreadsheet, for example, in which some of the cells are irrelevant, but their existence is significant). For some operations, I do not want the vector to contain a “missing value”. Perhaps I want to take a different course of action if there is a “missing value” in the set - perhaps another operation is being performed (thus, this is not an invalid state).

This original code will look something like this:

 const double MISSING_VALUE = 1.3579246e123; using std::vector; vector<double> missingAllowed(1000000, MISSING_VALUE); vector<double> missingNotAllowed(1000000, MISSING_VALUE); // ... populate missingAllowed and missingNotAllowed with (user) data... for (vector<double>::iterator it = missingAllowed.begin(); it != missingAllowed.end(); ++it) { if (*it != MISSING_VALUE) *it = sqrt(*it); // sqrt() could be any operation } for (vector<double>::iterator it = missingNotAllowed.begin(); it != missingNotAllowed.end(); ++it) { if (*it != MISSING_VALUE) *it = sqrt(*it); else *it = 0; } 

Note that a check for the “missing value” must be performed for each iteration of the loop . Although in most cases I understand that the sqrt function (or any other mathematical operation) is likely to overshadow this check, there are times when the operation is minimal (maybe just adding), and the check is expensive. Not to mention the fact that the “missing value” takes the legal value of input outside the game and can cause errors if the calculation legitimately reaches this value (it is unlikely, although it may be). Also, in order to be technically correct, user input must be checked for compliance with this value, and an appropriate course of action must be taken. I find this solution inelegant and less optimal in performance. This is critical code, and we definitely don't have the luxury of parallel data structures or some kind of data element objects.

The NaN version will look like this:

 using std::vector; vector<double> missingAllowed(1000000, std::numeric_limits<double>::quiet_NaN()); vector<double> missingNotAllowed(1000000, std::numeric_limits<double>::signaling_NaN()); // ... populate missingAllowed and missingNotAllowed with (user) data... for (vector<double>::iterator it = missingAllowed.begin(); it != missingAllowed.end(); ++it) { *it = sqrt(*it); // if *it == QNaN then sqrt(*it) == QNaN } for (vector<double>::iterator it = missingNotAllowed.begin(); it != missingNotAllowed.end(); ++it) { try { *it = sqrt(*it); } catch (FPInvalidException&) { // assuming _seh_translator set up *it = 0; } } 

Explicit validation has now been fixed, and performance should be improved. I think all this will work if I can initialize the vector without touching the FPU registers ...

Also, I would suggest that any self-confident sqrt execution checks for NaN and immediately return NaN.

+46
c ++ floating-point ieee-754 visual-c ++ x87
Feb 11 2018-10-11T00
source share
3 answers

As I understand it, the purpose of signaling NaN is to initialize data structures, but, of course, initializing the runtime in C triggers the risk that NaN is loaded into the float register as part of the initialization, thereby initiating the signal, since the compiler does not know that this float value is needed copy using integer register.

I would hope that you could initialize the static value of the signal NaN, but even this will require some special processing by the compiler to avoid converting to a calm NaN. Perhaps you can use a little spell magic to avoid treating it as a float value during initialization.

If you wrote to ASM, that would not be a problem. but in C and especially in C ++, I think you will have to undermine the type system in order to initialize the variable using NaN. I suggest using memcpy .

+8
Feb 11 '10 at 21:51
source share

Using special values ​​(even NULL) can make your data a lot messier, and your code a lot messier. It would be impossible to distinguish the result of QNaN from the “special” value of QNaN.

You might be better off maintaining a parallel data structure to track validity, or perhaps have your FP data in a different (sparse) data structure to keep only valid data.

This is pretty general advice; special values ​​are very useful in some cases (for example, really hard memory or performance limitations), but as the context grows, they can cause more difficulties than they are worth.

+1
Feb 13 '10 at 6:05
source share

Could you just create const uint64_t, where the bits were tuned to signaling bits? as long as you treat it as an integer type, the nan alarm is no different from other integers. You can write it wherever you want using the pointer:

 Const uint64_t sNan = 0xfff0000000000000; Double[] myData; ... Uint64* copier = (uint64_t*) &myData[index]; *copier=sNan | myErrorFlags; 

Information about the bits to install: https://www.doc.ic.ac.uk/~eedwards/compsys/float/nan.html

+1
Aug 17 '15 at 18:55
source share



All Articles