Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JObject.Parse modifies end of floating point values

Tags:

json.net

var clientString = "{\"max\":1214.704958677686}";

JObject o = JObject.Parse(clientString);

var jsonString = o.ToString();

contents of jsonString:

{
  "max": 1214.7049586776859
}

this is both in visualizing the object and in doing ToString(). Note that the 686 has mysteriously been expanded to 6859 (precision added). This is a problem for us because the numbers are not exactly the same, and a hash function over the json later does not match.

like image 360
Dmitriy Myshkin Avatar asked Oct 21 '14 10:10

Dmitriy Myshkin


3 Answers

@Ilija Dimov is correct--JSON.NET parses JSON floats as doubles by default. If you still want to use JObject instead of creating a full blown POCO for deserialization, you can use a JsonTextReader and set the FloatParseHandling option:

var reader = new JsonTextReader(new StringReader(clientString));
reader.FloatParseHandling = FloatParseHandling.Decimal;

JObject obj = JObject.Load(reader);

Console.WriteLine(obj["max"].Value<decimal>()); // 1214.704958677686
like image 69
Andrew Whitaker Avatar answered Oct 04 '22 22:10

Andrew Whitaker


The reason your value is changed is because of the nature of floating point numbers in .NET. The JObject.Parse(clientString) method at some point executes the following line:

double d;
double.TryParse("1214.704958677686", NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out d);

where d represents the number that you get in the JObject.

As d is of type double and double is floating point number, you didn't get the value you expect. Read more about Binary floating point and .NET.

There is an option in JSON.NET for parsing floating point numbers as decimals and get the precision you need, but to do that you need to create custom class that matches your json string and deserialize the json. Something like this:

public class MyClass
{
    [JsonProperty("max")]
    public decimal Max { get; set; }
}


var obj = JsonConvert.DeserializeObject<MyClass>(clientString, new JsonSerializerSettings
          {
              FloatParseHandling = FloatParseHandling.Decimal
          });

By using this code sample, the value of max property won't be changed.

like image 42
Ilija Dimov Avatar answered Oct 04 '22 21:10

Ilija Dimov


You can experiment this behaviour just by parsing to float, double and decimal:

Assert.AreEqual(1214.705f,float.Parse("1214.704958677686"));
Assert.AreEqual(1214.7049586776859, double.Parse("1214.704958677686"));
Assert.AreEqual(1214.704958677686, decimal.Parse("1214.704958677686"));

So json.net is using double as an intermediate type. You can change this by setting FloatParseHandling option.

like image 22
jruizaranguren Avatar answered Oct 04 '22 21:10

jruizaranguren