Floating Point Representation¶
Representation of a 32-bit float:
We assume that a float, f, is an IEEE 754 single-precision floating point number, whose bits are arranged as follows:
31 (msb)
|
| 30 23
| | |
| | | 22 0 (lsb)
| | | | |
X XXXXXXXX XXXXXXXXXXXXXXXXXXXXXXX
s e m
If e is between 1 and 254, f is a normalized number:
s e-127
f = (-1) * 2 * 1.m
s -126
f = (-1) * 2 * 0.m
f = 0.0
Examples:
0 00000000 00000000000000000000000 = 0.0
0 01111110 00000000000000000000000 = 0.5
0 01111111 00000000000000000000000 = 1.0
0 10000000 00000000000000000000000 = 2.0
0 10000000 10000000000000000000000 = 3.0
1 10000101 11110000010000000000000 = -124.0625
0 11111111 00000000000000000000000 = +infinity
1 11111111 00000000000000000000000 = -infinity
0 11111111 10000000000000000000000 = NAN
1 11111111 11111111111111111111111 = NAN
Here is the bit-layout for a half number, h:
15 (msb)
|
| 14 10
| | |
| | | 9 0 (lsb)
| | | | |
X XXXXX XXXXXXXXXX
s e m
If e is between 1 and 30, h is a normalized number:
s e-15
h = (-1) * 2 * 1.m
S -14
h = (-1) * 2 * 0.m
h = 0.0
Examples:
0 00000 0000000000 = 0.0
0 01110 0000000000 = 0.5
0 01111 0000000000 = 1.0
0 10000 0000000000 = 2.0
0 10000 1000000000 = 3.0
1 10101 1111000001 = -124.0625
0 11111 0000000000 = +infinity
1 11111 0000000000 = -infinity
0 11111 1000000000 = NAN
1 11111 1111111111 = NAN
Converting from half to float is performed by default using a lookup table. There are only 65,536 different half numbers; each of these numbers has been converted and stored in a table pointed to by the imath_half_to_float_table
pointer.
Prior to Imath v3.1, conversion from float to half was accomplished with the help of an exponent look table, but this is now replaced with explicit bit shifting.
Conversion via Hardware:
For Imath v3.1, the conversion routines have been extended to use F16C SSE instructions whenever present and enabled by compiler flags.
Conversion via Bit-Shifting
If F16C SSE instructions are not available, conversion can be accomplished by a bit-shifting algorithm. For half-to-float conversion, this is generally slower than the lookup table, but it may be preferable when memory limits preclude storing of the 65,536-entry lookup table.
The lookup table symbol is included in the compilation even if IMATH_HALF_USE_LOOKUP_TABLE
is false, because application code using the exported half.h
may choose to enable the use of the table.
An implementation can eliminate the table from compilation by defining the IMATH_HALF_NO_LOOKUP_TABLE
preprocessor symbol. Simply add:
#define IMATH_HALF_NO_LOOKUP_TABLE
half.h
, or define the symbol on the compile command line.
Furthermore, an implementation wishing to receive FE_OVERFLOW
and FE_UNDERFLOW
floating point exceptions when converting float to half by the bit-shift algorithm can define the preprocessor symbol IMATH_HALF_ENABLE_FP_EXCEPTIONS
prior to including half.h
:
#define IMATH_HALF_ENABLE_FP_EXCEPTIONS
Testing on a Core i9, the timings are approximately:
half to float
table: 0.71 ns / call
no table: 1.06 ns / call
f16c: 0.45 ns / call
float-to-half:
original: 5.2 ns / call
no exp table + opt: 1.27 ns / call
f16c: 0.45 ns / call
Note: the timing above depends on the distribution of the floats in question.