4

When I write a float to a buffer, it does not read back the same value:

> var b = new Buffer(4);
undefined
> b.fill(0)
undefined
> b.writeFloatBE(3.14159,0)
undefined
> b.readFloatBE(0)
3.141590118408203
> 
(^C again to quit)
> 

Why?

EDIT:

My working theory is that because javascript stores all numbers as double precision, it's possible that the buffer implementation does not properly zero the other 4 bytes of the double when it reads the float back in:

> var b = new Buffer(4)
undefined
> b.fill(0)
undefined
> b.writeFloatBE(0.1,0)
undefined
> b.readFloatBE(0)
0.10000000149011612
>

I think it's telling that we have zeros for 7 digits past the decimal (well, 8 actually) and then there's garbage. I think there's a bug in the node buffer code that reads these floats. That's what I think. This is node version 0.10.26.

Kevin
  • 24,871
  • 19
  • 102
  • 158
  • It's a float, not a double. If you need the precision of a double, write a double instead. – Qantas 94 Heavy Nov 24 '14 at 04:27
  • Probably should be using the byte size specific read / write methods. – Ryan Nov 24 '14 at 04:27
  • @self: I'm not sure I understand. Are you saying that `writeFloatBE` should not be used to write a floating point number to a buffer? – Kevin Nov 24 '14 at 04:29
  • @Kevin: [`writeDoubleBE`](http://nodejs.org/api/buffer.html#buffer_buf_writedoublebe_value_offset_noassert) is your friend. – Qantas 94 Heavy Nov 24 '14 at 04:29
  • @Qantas: if I'm implementing a platform neutral wire format, and the other side is expecting a 4 byte float, I can't exactly do that, can I? – Kevin Nov 24 '14 at 04:31
  • @Qantas: but you're right, `writeDoubleBE` works fine. – Kevin Nov 24 '14 at 04:31
  • @Kevin: well... that's the closest to 3.14159 for a float. If you're looking for some way to display the least number of decimal places to represent a single-precision float, that's a slightly different question. – Qantas 94 Heavy Nov 24 '14 at 04:33
  • I still don't get it. How is `3.141590118408203` closer to `3.14159`? It's not like I'm dividing numbers, which I would understand could create long approximations. It seems to me the implementation is not zero filling, or is reading in garbage, or something like that. – Kevin Nov 24 '14 at 04:36
  • It's not closer, just the *closest* representation that is achievable as a float. This is an unfortunate restriction inherent to single-precision floats. – Robert Mitchell Nov 24 '14 at 04:42
  • @RobertMitchell: I don't believe it, see my update on the question for the reason why. – Kevin Nov 24 '14 at 05:03

2 Answers2

5

Floating point numbers ("floats") are never a fully-accurate representation of a number; this is a common feature that is seen across multiple languages, not just JavaScript / NodeJS. For example, I encountered something similar in C# when using float instead of double.

Double-precision floating point numbers are more accurate and should better meet your expectations. Try changing the above code to write to the buffer as a double instead of a float:

var b = new Buffer(8);
b.fill(0);
b.writeDoubleBE(3.14159, 0);
b.readDoubleBE(0);

This will return:

3.14159

EDIT:

Wikipedia has some pretty good articles on floats and doubles, if you're interested in learning more:

SECOND EDIT:

Here is some code that illustrates the limitation of the single-precision vs. double-precision float formats, using typed arrays. Hopefully this can act as proof of this limitation, as I'm having a hard time explaining in words:

var floats32 = new Float32Array(1),
    floats64 = new Float64Array(1),
    n = 3.14159;

floats32[0] = n;
floats64[0] = n;

console.log("float", floats32[0]);
console.log("double", floats64[0]);

This will print:

float 3.141590118408203
double 3.14159

Also, if my understanding is correct, single-precision floating point numbers can store up to 7 total digits (significant digits), not 7 digits after the decimal point. This means that they should be accurate up to 7 total significant digits, which lines up with your results (3.14159 has 6 significant digits, 3.141590118408203 => first 7 digits => 3.141590 => 3.141590 === 3.14159).

Robert Mitchell
  • 1,334
  • 8
  • 10
  • 1
    please see comments below the question. I'm implementing a wire format, so the other side expects 4 bytes. I can't just send 8 bytes. And, floats are precise to 7 digits past the decimal (vs. 17 for doubles). I've only got 5 digits past the decimal, so I still don't buy into this "doubles are more accurate" argument. – Kevin Nov 24 '14 at 04:41
  • Interesting...I'll have to think about this one for a bit and get back to you :) – Robert Mitchell Nov 24 '14 at 04:44
  • You can adjust `n` in my code sample above to verify the "7 total digits" piece. For example, `n = 12345678.12345678` results in `float 12345678` `double 12345678.12345678` – Robert Mitchell Nov 24 '14 at 06:35
1

readFloat in node is implemented in c++ and bytes are interpreted exactly the way your compiler stores/reads them. I doubt there is a bug here. What I think is that "7 digits" is incorrect assumption for float. This answer suggest 6 digits (and it's the value of std::numeric_limits<float>::digits10 ) so the result of readFloatBE is within expected error

Community
  • 1
  • 1
Andrey Sidorov
  • 24,905
  • 4
  • 62
  • 75