Search code examples
javascriptnode.jsieee-754

In NodeJS, how to encode a 32 bits float number from a Buffer to a (json) string preserving original precision?


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 }.


Solution

  • 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.