According to IEEE floating point definition , double
uses the following encoding (assuming 64
bits):
1 sign bit + 11 exponent bits + 52 mantissa
Some interesting special values are:
1 2 3 4 * INF: 0 {1}*11 {0}*52 // positive sign, set exp and unset mantissa * -INF: 1 {1}*11 {0}*52 // negative sign, set exp and unset mantissa * Quiet NaN: _ {1}*11 1 {_}*51 // any sign, set exp and one bit after exp, and the rest bits are free * Singal NaN: _ {1}*11 0 [^{0}*51] // any sign, set exp, and the bit after expo is unset, and the rest bits contains at least one bit set
Because there are 51 free bits in NaN case, we can embed additional info inside it, hence the name NaN boxing.
The code is more or less converted from the C++ code snippet at
https://nikic.github.io/2012/02/02/Pointer-magic-for-efficient-dynamic-value-representations.html , with the different treatment on handling nan
. On
my box (ubuntu 14.04 using clang 3.8), sqrt(-1)
produces -nan
, and it’s considered as a double.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 #include <stdio.h> #include <math.h> #include <stdbool.h> #include <stdint.h> #include <assert.h> #include <stdlib.h> typedef union { double as_double; uint64_t bits; int32_t as_int32; } value_t ; static const uint64_t max_double = 0xFFF8 ULL << 48 ;static const uint64_t ptr_mask = 0xFFF9 ULL << 48 ;static const uint64_t int_mask = 0xFFFA ULL << 48 ;bool is_neg_zero (double x) { return (int )x == 0 && (*(uint64_t *)&x >> 63 ) == 1 ; } value_t from_double (double x) { value_t v; if (x == (int32_t )x && !is_neg_zero(x)) { v.bits = (unsigned long ) (int32_t )x | int_mask; return v; } v.as_double = x; return v; } value_t from_ptr (void *p) { uintptr_t p_i = (uintptr_t ) p; assert((p_i & ptr_mask) == 0 ); return (value_t ) { .bits = p_i | ptr_mask }; } bool is_double (value_t v) { return v.bits <= max_double; } bool is_ptr (value_t v) { return (v.bits & ptr_mask) == ptr_mask; } bool is_int32 (value_t v) { return (v.bits & int_mask) == int_mask; } void test (value_t v) { if (is_int32(v)) { printf ("v is int: %d\n" , v.as_int32); return ; } if (is_double(v)) { printf ("v is double: %lf\n" , v.as_double); return ; } if (is_ptr(v)) { printf ("v is ptr: %p\n" , (void *) (v.bits & ~ptr_mask)); return ; } puts ("IMPOSSIBLE" ); assert(0 ); } int main () { value_t v; v = from_double(0.3 ); test(v); v = from_double(3 ); test(v); v = from_ptr(malloc (1 )); test(v); v = from_double(1.0 /0 ); test(v); v = from_double(sqrt (-1 )); test(v); return 0 ; }
1 2 3 4 5 v is double: 0.300000 v is int: 3 v is ptr: 0x1d77010 v is double: inf v is double: -nan
Reference
https://www.doc.ic.ac.uk/~eedwards/compsys/float/nan.html