Created
August 22, 2020 13:11
-
-
Save bollu/c87c68da2c57bb725ae74ce172f73799 to your computer and use it in GitHub Desktop.
Use NaN punning to box an int32_t and a double together
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <assert.h> | |
#include <iostream> | |
#include <limits> | |
using namespace std; | |
#define INT32_TAG 0xfffffffe | |
// https://github.com/WebKit/webkit/blob/950143da027e80924b4bb86defa8a3f21fd3fb1e/Source/JavaScriptCore/runtime/JSCJSValueInlines.h | |
// https://github.com/WebKit/webkit/blob/950143da027e80924b4bb86defa8a3f21fd3fb1e/Source/JavaScriptCore/runtime/JSCJSValue.h | |
// https://en.wikipedia.org/wiki/NaN | |
union PunDouble { | |
double d; | |
struct { | |
uint64_t m : 51; | |
uint32_t qnan: 1; | |
uint32_t e : 11; | |
uint32_t s : 1; | |
} bits; | |
PunDouble(double d) : d(d) {}; | |
PunDouble(uint32_t s, uint32_t e, uint64_t m) { | |
bits.s = s; | |
bits.e = e; | |
bits.qnan = 1; | |
bits.m = m; | |
} | |
}; | |
union PunInt { | |
int32_t i; | |
uint32_t bits; | |
PunInt(int32_t i): i(i) {}; | |
}; | |
using namespace std; | |
struct Box { | |
inline bool is_int() const { | |
auto pd = PunDouble(d); | |
return pd.bits.e == 0b11111111111 && pd.bits.qnan == 1; | |
} | |
inline bool isdouble() const { | |
auto pd = PunDouble(d); | |
return (pd.bits.e != 0b11111111111) || (pd.bits.qnan == 0); | |
} | |
int32_t get_int() const { | |
assert(is_int()); | |
uint64_t m = PunDouble(d).bits.m; return PunInt(m).i; | |
} | |
double get_double() const { assert(isdouble()); return d; } | |
Box operator +(const Box &other) const; | |
static Box mk_int(int32_t i) { | |
return Box(PunDouble(1, 0b11111111111, PunInt(i).bits).d); | |
} | |
static Box mk_double(double d) { return Box(d); } | |
double rawdouble() const { return d; } | |
private: | |
double d; Box(double d) : d(d) {} | |
}; | |
// = 64 bits | |
Box Box::operator + (const Box &other) const { | |
if (isdouble()) { | |
assert(other.isdouble()); | |
return Box::mk_double(d + other.d); | |
} | |
else { | |
assert(is_int()); | |
assert(other.is_int()); | |
return Box::mk_int(get_int() + other.get_int()); | |
} | |
} | |
ostream &operator << (ostream &o, const Box &b) { | |
if (b.isdouble()) { return o << "[" << b.get_double() << "]"; } | |
else { return o << "[" << b.get_int() << "]"; } | |
} | |
int32_t randint() { return (rand() %2?1:-1) * (rand() % 100); } | |
int32_t main() { | |
// generate random integers, check that addition checks out | |
srand(7); | |
for(int32_t i = 0; i < 1000; ++i) { | |
const int32_t i1 = randint(), i2 = randint(); | |
const Box b1 = Box::mk_int(i1), b2 = Box::mk_int(i2); | |
cout << "i1:" << i1 << " b1:" << b1 << " b1.double:" << b1.rawdouble() << " b1.get_int:" << b1.get_int() << "\n"; | |
cout << "i2:" << i2 << " b2:" << b2 << " b2.double:" << b2.rawdouble() << " b2.get_int:" << b2.get_int() << "\n"; | |
assert(b1.is_int()); | |
assert(b2.is_int()); | |
assert(b1.get_int() == i1); | |
assert(b2.get_int() == i2); | |
Box b3 = b1 + b2; | |
assert(b3.is_int()); | |
assert(b3.get_int() == i1 + i2); | |
} | |
for(int32_t i = 0; i < 1000; ++i) { | |
const int32_t p1 = randint(), q1=randint(), p2 = randint(), q2=randint(); | |
const double d1 = (double)p1/(double)q1; | |
const double d2 = (double)p2/(double)q2; | |
const Box b1 = Box::mk_double(d1); | |
const Box b2 = Box::mk_double(d2); | |
cout << "d1: " << d1 << " | b1: " << b1 << "\n"; | |
cout << "d2 " << d2 << " | b2: " << b2 << "\n"; | |
assert(b1.isdouble()); | |
assert(b2.isdouble()); | |
assert(b1.get_double() == d1); | |
assert(b2.get_double() == d2);; | |
Box b3 = b1 + b2; | |
assert(b3.isdouble()); | |
assert(b3.get_double() == d1 + d2); | |
} | |
return 0; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment