teem | / | air |
IEEE 754 floats |
Sadly, this requires some care. Ideally, we could define a macro like this:
This is supposed to work because x isn't equal to itself if x is a special value, but many compilers will reduce this to true, even at low optimization levels. So, we could try this:#define AIR_EXISTS(x) ((x) == (x))
For special values, x - x is not 0, so this should work. But some compilers (such as 64-bit Irix6 with "-Ofast", and Visual Studio) again decree that AIR_EXISTS() is true for any possible value of x. To be completely thorough, one has to actually look at the bit patterns of the values, at which point you have to be specific to the float/ double type, and correct about their byte-sizes:#define AIR_EXISTS(x) (!((x) - (x)))
We want to avoid false negatives for doubles with values greater than FLT_MAX, so if we had to pick one of these to wrap in a single function that was used for both float and double, it would be the second. This is the approach currently taken: on machines for which (!((x) - (x))) doesn't work because of optimizations, we resort to casting the value to double, and looking at its bits (done in airExists_d()). Thus, in air.h we find:#define AIR_EXISTS_F(x) ((*(unsigned int*)&(x) & 0x7f800000) != 0x7f800000) #define AIR_EXISTS_D(x) ( \ (*(airULLong*)&(x) & AIR_ULLONG(0x7ff0000000000000)) \ != AIR_ULLONG(0x7ff0000000000000))
Hopefully, at some soon, teem will be made with the assistance of a highly customized GNU configure script which will determine these things on a per-architecture, per-optimization basis. The above solution works on all platforms that teem compiles on, using the optimization levels that the makefiles supply (generally -O2). A function called airSanity() makes sure that all IEEE 754-related macros (such as AIR_EXISTS()) work correctly.#ifdef WIN32 #define AIR_EXISTS(x) (airExists_d(x)) #else #define AIR_EXISTS(x) (!((x) - (x))) #endif
One thing to keep in mind: Section 6.2 of IEEE 754 guarantees that a NaN stored in a float will stay a NaN when stored in a double, and vice versa. Thus, you need not be cautious about the "precision" of your NaN. The same doesn't exactly hold for infinity, though.
How to get NaN: If you want a NaN, so that you can save it into a floating point variable, perhaps as an indicator for "don't know", or "uninitialized", you have a few options.
There are in fact two kinds of NaNs, the signaling NaN and the quiet NaN. Nothing in teem attempts to use the floating point exception handler to work with signaling NaNs, because there isn't a cross-platform C-language API to the floating point hardware. Also, nothing in teem attempts to use the integer value stored in the fraction field of the NaN to represent different kinds of conditions of NaN, so effectively, a NaN is a NaN is a NaN. However, an effort is made to make AIR_NAN be what the local architecture considers a quiet NaN, in case using a signalling NaN could have an impact on performace.
To generate an infinity:
Values (of type int) from this enum are used in conjunction with:enum { airFP_Unknown, /* 0: nobody knows */ airFP_SNAN, /* 1: signalling NaN */ airFP_QNAN, /* 2: quiet NaN */ airFP_POS_INF, /* 3: positive infinity */ airFP_NEG_INF, /* 4: negative infinity */ airFP_POS_NORM, /* 5: positive normalized non-zero */ airFP_NEG_NORM, /* 6: negative normalized non-zero */ airFP_POS_DENORM, /* 7: positive denormalized non-zero */ airFP_NEG_DENORM, /* 8: negative denormalized non-zero */ airFP_POS_ZERO, /* 9: +0.0, positive zero */ airFP_NEG_ZERO, /* 10: -0.0, negative zero */ airFP_Last /* after the last valid one */ };
These generate float or double values of the given class.float airFPGen_f(int cls); double airFPGen_d(int cls);
These categorize a given floating-point value into one of the classes.int airFPClass_f(float val); int airFPClass_d(double val);
Let you convert between a float and the three integral fields which comprise it: 1-bit sign, 8-bit exponent, and 23-bit fraction.float airFPPartsToVal_f(int sign, int exp, int frac); void airFPValToParts_f(int *signP, int *expP, int *fracP, float v);
Same thing, but for double. airULLong is just another name for unsigned long long, which Microsoft calls an "unsigned __int64". A double is a 1-bit sign, 11-bit exponent, 52-bit fraction.double airFPPartsToVal_d(int sign, int exp, airULLong frac); void airFPValToParts_d(int *signP, int *expP, airULLong *fracP, double v);
Sample output from airFPFprintf_f(stdout, 42):void airFPFprintf_f(FILE *file, float val); void airFPFprintf_d(FILE *file, double val);
Sample output from airFPFprintf_d(stdout, 42):42.000000: class 5; 0x42280000 = sign:0x0, exp:0x84, frac:0x280000 = S < . . Exp . . > < . . . . . . . . . Frac. . . . . . . . . . > 0 1 0 0 0 0 1 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Command-line demos are teem/src/air/test/floatprint.c and teem/src/air/test/doubleprint.c42.000000: class 5; 0x4045000000000000 = sign:0x0, exp:0x404, frac:0x5000000000000 = S<...Exp...><.......................Frac.......................> 0100000001000101000000000000000000000000000000000000000000000000