The input is a buffer containing single precision float numbers (32 bits, little endian).
What I want to produce is a JSON string containing those numbers, preserving the exact same values without precision change.
The problem is: as soon as the value is stored in a JS number
, it is converted to 64bits, introducing precision changes.
For example:
const buffer = Buffer.from("cdcccc3d", "hex"); // cdcccc3d is 0.1 in 32 bits float LE
console.log(buffer.readFloatLE(0)); // 0.10000000149011612 => bad
How to get this value as a string preserving the original precision with no conversion? Here: "0.1"
.
Bonus point if I manage to produce a JSON string containing the number with original precision: { "value": 0.1 }
.
The little-endian bytes cd cc cc 3d are, in big-endian order, 3d cc cc cd, or the bits 0011 1101 1100 1100 1100 1100 1100 1101.
In the IEEE-754 binary32 encoding, 0 is the sign bit, 011 1101 1 are the exponent field bits, and 100 1100 1100 1100 1100 1101 are the significand field bits.
A 0 sign bit means positive.
The exponent field bits 011 1101 1 are 123 as raw binary. Exponents are encoded with a bias of 127, so the represented exponent is 123−127 = −4.
Since the exponent field is not zero (or all ones, which is used for infinities and NaNs), a 1 is prefixed to the significand field bits, forming 1100 1100 1100 1100 1100 1101. As raw binary, this is 13,421,773. Significands are encoded with a scale of 223, so the represented significand is 13,421,773/223.
Combining the sign, exponent, and significand, the represented value is +2−4•13,421,773/223 = 0.100000001490116119384765625. This is the exact value represented, with no error. So you can see that the numeral produced for it, “0.10000000149011612” is accurate, as far as it goes.