JsonUtility serializes floats with way too many digits

I need to serialize a Vector3 to Json. For this i want to use the build in JsonUtility. But JsonUtility converts a Vector3 to floats with a lot of decimal places. I know that this is the way a float works but it is not practical for serialization to a file. The files will become unnecessarily large because of all the digits.

Here an example:

Debug.Log(JsonUtility.ToJson(new Vector3(0.24f, 0.5f, 0.75f)));

will output:

{"x":0.23999999463558198,"y":0.5,"z":0.75}

can i do something to make this more efficient? I used Json Net before where this was no problem. However Json Net is a lot slower than JsonUtility (see here) and i would like to use as little 3rd party libraries as possible.

You’ve found the float problem.

You could try a double, which may seem counter intuitive, and doesn’t always solve the problem, but here’s what’s happening to you:

float isn’t accurate. There is a limited amount of storage, and it is typically squeezed into 32 bits. It is fashioned with a two part design, a mantissa and exponent. It is the binary version of scientific notation (like 2.4 e-1). However, the mantissa is limited to fewer than 32 bits. The exponent takes up some of those bits. In the standard IEEE format for this (which I think C# is using here), this means you have 24 bits for the precision (a base 2 version of the digits involved), and 8 bits for the exponent (expressing the power of 2 those digits should be shifted), and both are signed (so you have negative exponents for shifts toward very small values).

As a result of this encoding, the representation isn’t exactly. .24 becomes a close approximation that when converted into base 10 looks like a garbled mess.

It is, however, normal, and even internally, the number .24 may indeed be “known” to your C# code as the value JSON is showing, and not actually .24f.

Doubles have more precision, and therefore have a chance to be more accurate, but in practice all that really means is that the base 10 representations are different (maybe .24 looks exact, but .242 doesn’t). There are various formats, some considered non-standard.

This goes all the way down to the CPU, as it uses at least some standard for these representations which may be the same used by the language, and thus fundamentally there’s nothing you can actually do if you’re going to use floats.

You could, however, use other numbering systems. Fixed point values could be a choice, where an integer (possibly 64 bits) represents units such that you have a fixed number of decimal values that never “float” like this. For example, you could decide you want 4 digits of precision, so you’d consider all integers 1000 units, so that 240 is actually .24.

Other than that, this is a fundamental issue for which there are a wide range of solutions you’ll find all over the web, and can choose among many that might better suit your preference for a particular application.

Or, like many of us, just accept that’s how it works and forget about it.

Just for completeness: If you don’t care about how the value is stored in the json data and all you need is to store and read the data from Unity you can store the value either as integer or as string. This can be easily done with Unity’s ISerializationCallbackReceiver interface. You could even wrap the serialization into a seperate struct / class like this:

public struct FloatWrapper : ISerializationCallbackReceiver
{
    [System.NonSerialized]
    public float someValue;
    [SerializeField]
    private int _someValue;
    public void OnAfterDeserialize()
    {
        someValue = _someValue / 1000f;
    }
    public void OnBeforeSerialize()
    {
        _someValue = (int)(someValue * 1000);
    }
}

In your code you just use the public float value. However when serialized we actually store the value as integer so you basically preserve 3 decimal digits behind the decimal point. So a value like “25.12” would be stored as 25120. Another way would be to use a string as serialized value and format the float as you like using the serialization callbacks. However in this case the value in the json data would be a string and not a json number

    // [ .... ]
    private string _someValue;
    public void OnAfterDeserialize()
    {
        float.TryParse(_someValue, out someValue);
    }
    public void OnBeforeSerialize()
    {
        _someValue = someValue.ToString("UseYourDesiredFormat");
    }
    // [ .... ]

Of course you have to take care of that the deserialization of the string value into a float works. Of course as a string value you have two additional quote characters in your json data.