Http.jsonencode adding floating point digits?


#1

At some point in the last few months http.jsonencode started outputting some strange numbers

local temp = {“value”: sysdataarray[index],“key”: datakeyarray[index]};
local dataString = http.jsonencode(temp);
server.log(temp[“value”])
server.log(dataString)

log output:

|[Agent] |39.7|
|[Agent] |{ “value”: 39.700001, “key”: “Trunk Line Temperature” }|

In this case the values are temperature data with a single floating point precision e.g. 39.7

The http.jsonencode output is 39.700001

huh?

some more examples

38.6 = 38.599999
38.4 = 38.400002
38.7 = 38.700001
39.1 = 39.099998

it’s not even consistent…Any ideas?


#2

That appears to just be 32 bit single precision float encoding.

The nearest you can encode 39.7 into binary is actually 39.700000762939453125, which is being rounded to 39.700001 for the json encode.

If you encode a number where the mantissa can be exactly represented in binary (eg 39.125) you will see an exact value.

See https://www.h-schmidt.net/FloatConverter/IEEE754.html for the gory details… as to behavior changing, that’s possible as previously we were missing a digit at the end. @rogerlipscombe ?


#3

The extra precision was added to the agent on Sep 19; it was deployed to the developer server on Oct 19 (and one of the production servers on Oct 8).

As Hugo points out, Squirrel uses IEEE-754 single precision binary floating point numbers. They can’t exactly represent most decimal floating point numbers.

server.log() appears to be truncating to one decimal place; http.jsonencode() previously rounded to 6 digits of precision. It now rounds to 8 (which is the maximum that IEEE-754 single precision floats can represent).


#4

If you wanted your JSON to show less precision, eg 1 decimal place, you could use the squirrel JSON encoder class here https://developer.electricimp.com/libraries/utilities/jsonencoder but alter it, eg changing this bit:

  case "integer":
  case "float":
  case "bool":
    r += val;
    break;

to…

  case "float":
    r += format("%.1f", val);
    break;

  case "integer":
  case "bool":
    r += val;
    break;

#5

Thank you Hugo and Roger! Much appreciated!