gccfloating-pointc99built-inisnan

Does GCC's __builtin_isnan do something other than isnan?


In the GCC built-ins description, it says:

GCC provides built-in versions of the ISO C99 floating-point comparison macros that avoid raising exceptions for unordered operands. They have the same names as the standard macros ( isgreater, isgreaterequal, isless, islessequal, islessgreater, and isunordered) , with _builtin prefixed. We intend for a library implementor to be able to simply #define each standard macro to its built-in equivalent. In the same fashion, GCC provides fpclassify, isfinite, isinf_sign, isnormal and signbit built-ins used with _builtin prefixed. The isinf and isnan built-in functions appear both with and without the _builtin prefix.

So, I'm not quite able to parse this. When should floating-point comparisons raise exceptions? Does the C standard mandate they do? Mandate they don't? Doesn't mandate anything? And - does __builtin_isnan() act differently than isnan() ?


Solution

  • GCC's builtins of standard library functions are mostly for optimization, when certain arguments or environment constraints can be known at compile time. __builtin_isnan() works in the similar fashion.

    The main purpose of __builtin_isnan() is to permit optimizing isnan(x) into (x != x) when signaling NaN support is disabled (i.e. -fno-signaling-nans, which is the default).

    The behaviors of isnan(x) and (x != x) are not identical. There are differences when a signaling NaN is being evaluated.

    (Above is the main answer to the question. Below are more technical details.)


    IEEE 754 (the standard) defines "quiet" NaNs and "signaling" NaNs that could be a headache for you to get them thoroughly. (I mean it.)

    If you are new to IEEE floating points and their compare operations, here are some basics:

    The "quiet" and "signaling" terms are relative, and I add them in scare quotes because a "quiet" compare isn't always quiet, and "signaling" doesn't always signal.

    The "quiet" comparisons in IEEE 754 map to these in C: (x == y), isgreater, isgreaterequal, isless, islessequal, isunordered.

    The "signaling" comparisons in IEEE 754 map to these in C: iseqsig, (x > y), (x >= y), (x < y) and (x <= y). There is no "signaling" variant of isunordered as that won't make sense.

    For "quiet" comparisons they don't throw FP exceptions with a quiet NaN operand. But they do throw "Invalid Operation" (FE_INVALID) exception when any of the operands is a signaling NaN.

    For "signaling" comparisons they throw exceptions when any of operands is a NaN, regardless of its signaling bit. (I.e. they throw exceptions on quiet NaNs, too.)

    Then there are functions that never throw exceptions, even with the presence of a signaling NaN. fpclassify functions, including isnan, are this kind. (These are also standard operations in IEEE 754, although different languages provide different APIs for these.)

    In this Stack Overflow answer about isnan() and (x != x) I made a quick comparison table:

                  | isnan(x) | (x != x)  | (x >= x)   | isgreaterequal(x,x)
    --------------+----------+-----------+------------+--------------------
    -Wfloat-equal | no warn  | warn      | no warn    | no warn
    --------------+----------+-----------+------------+--------------------
    Finite number | false    | false     | true       | true
    Infinity      | false    | false     | true       | true
    NaN (quiet)   | true     | true      | FPE; false | false
    SNaN          | true     | FPE; true | FPE; false | FPE; false
    

    Notes: true and false in the table refer to the nonzero and 0 return values respectively for the macros (their return types are int, not bool). "FPE" indicates that the macro or expression produces "Invalid Operation" exception.

    So you can get the idea on how isnan, !=, >=, and isgreaterequal behave on different kind of floating point values.