((double) float) results different values in Unity and MSVS

Unity:

MSVS:

so math error will accumulating

how to deal with it ?

and how to explain this?

Unity:

but in MSVC no problem

Math error always accumulates when working with floats.

Now… the error you demonstrate here would only matter if you were passing binary data between unity and… MSVS. I’m assuming that’s microsoft visual studio… in which case you mean the MS .net runtime… so it’s mono vs .net runtimes, not unity vs msvs.

And yeah… when would you be passing data between the 2?

Huh?

Explain what? A return line? No problem in MSVC? What’s MSVC??? Visual Code? Why so many disparate names going on here???

Also, you can use code tags when showing us code:

Instead of uploading screencaps.

You generally shouldn’t do exact equality operations on floats because they are prone to rounding errors. Instead you can use Mathf.Approximately() - Unity - Scripting API: Mathf.Approximately

what rounding errors in this case ?

x and x2 won’t be equal. You took a double, stuck it in a float (less discrete space for storage), then expanded it back out to a double.

You lost information.

You don’t magically get information back because you convert back to a double.

What the hell? Use code tags. Have some respect for forum users or I’ll lock this thread.

Never use screenshots for code.

1 Like

Short answer: It’s complicated and floats are a mess, so just don’t use equality with them, always use Mathf.Approximately. :wink: For a longer answer, look at the responses to this person who noticed the same problem:

http://answers.unity3d.com/questions/121304/c-floating-point-confusion.html

It’s not quite that simple… x is a double that is just a cast of the float on the right side to a double (note the “F” in the number, which means the right hand side is a float). x2 is a cast of the same exact float to a double, except it goes through the intermediate step of being assigned a variable (fx2) before it is cast. So in theory, they should be the same - they are the same float value both cast to a double. But you can’t trust floats even if they seem like they should be identical.

Good point, overlooked that the first one is a float divided by 7.

But yeah, float error collects over every operation. OP calculates x and x2 differently… so they’re not going to be equal.

You typically don’t need to. The accumulation error from floating point operations is typically so small that its not worth worrying about. Floats tend to loose precision after about 8 or 9 digits. If you consider a timer running at 60 frames per second, the maximum possible error accumulation is about one hundredth of a second across a full day. Using an approximation instead of an equality is more then enough to deal with this. In a multiplayer situation you simply force the players to match each other at regular intervals.

Where floats will kill you is their floating nature. Adding a small float to a large float has no effect*. This means that a simple timer running at 60 FPS in Unity will fail after about 2.5 days. And physics breaks down when you get too far from the origin. And so on. There are dozens of threads talking about ways to approach solving this problem.

*Formally stated it sounds like this: if a < b * 10 ^ -8 then a + b = b.

I post screenshot as proof of breakpoint.

I think this is bug, because unity have different behaviour than MSVS in same code

It’s not a bug.

C# Specification,
4.1.6 Floating point types

its a bug
same cast in both lines.
First result of divide is FLOAT and placed to double (x)
Second result of divide is FLOAT and placed to FLOAT (fx2) and then placed to double (x2).
So variables must be identical, and they are identical in MS .NET, but not in Unity.

If you even so much as look at a float, it might change.

Never ever assume that two floats are equal, unless you explicitly assigned them. And maybe not even then (depending on what you tried to assign to them). See Kiwasi’s last post.

I wouldn’t call it a bug.

I’d call it a difference in how the mono compiler and the .net compiler work.

NOTE - mono and .net are independent of one another. There is no one right way to compile C# to CIL. Each implementation may approach similar problems in different ways.

The problem you’re seeing here has to do with how Visual Studio compiles your code, and how Unity compiles your code.

Unity’s compiler aggressively optimizes trivial code. Where as the .net compiler does not.

Case in point here is code I wrote in both Visual Studio and Unity:

Visual Studio:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Console01
{
    class Program
    {
        static void Main(string[] args)
        {
            if(Foo())
            {
                Console.WriteLine("NOT EQUAL");
            }

            Console.ReadLine();
        }

        static bool Foo()
        {
            double a = 3.14159274f / 7;

            float x = 3.14159274f / 7;
            double b = (double)x;

            return a != b;
        }
    }
}

Unity:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class zTest01 : MonoBehaviour {

    // Use this for initialization
    void Start () {
        if (Foo())
        {
            Debug.Log("NOT EQUAL");
        }
    }


    static bool Foo()
    {
        double a = 3.14159274f / 7;

        float x = 3.14159274f / 7;
        double b = (double)x;

        return a != b;
    }

}

Now, I built both, and then decompiled them using Dotpeek:

The CIL for just the Foo method:…

Visual Studio:

  .method private hidebysig static bool
    Foo() cil managed
  {
    .maxstack 2
    .locals init (
      [0] float64 a,
      [1] float32 x,
      [2] float64 b,
      [3] bool V_3
    )

    // [22 9 - 22 10]
    IL_0000: nop       

    // [23 13 - 23 40]
    IL_0001: ldc.r8       0.448798954486847
    IL_000a: stloc.0      // a

    // [25 13 - 25 39]
    IL_000b: ldc.r4       0.448799
    IL_0010: stloc.1      // x

    // [26 13 - 26 34]
    IL_0011: ldloc.1      // x
    IL_0012: conv.r8   
    IL_0013: stloc.2      // b

    // [28 13 - 28 27]
    IL_0014: ldloc.0      // a
    IL_0015: ldloc.2      // b
    IL_0016: ceq       
    IL_0018: ldc.i4.0 
    IL_0019: ceq       
    IL_001b: stloc.3      // V_3
    IL_001c: br.s         IL_001e

    // [29 9 - 29 10]
    IL_001e: ldloc.3      // V_3
    IL_001f: ret       

  } // end of method Program::Foo

Unity:

  .method private hidebysig static bool
    Foo() cil managed
  {
    .maxstack 2
    .locals init (
      [0] float64 V_0,
      [1] float32 V_1,
      [2] float64 V_2
    )

    // [20 5 - 20 51]
    IL_0000: ldc.r8       0.448798963001796
    IL_0009: stloc.0      // V_0
    IL_000a: ldc.r4       0.448799
    IL_000f: stloc.1      // V_1
    IL_0010: ldloc.1      // V_1
    IL_0011: conv.r8   
    IL_0012: stloc.2      // V_2
    IL_0013: ldloc.0      // V_0
    IL_0014: ldloc.2      // V_2
    IL_0015: ceq       
    IL_0017: ldc.i4.0 
    IL_0018: ceq       
    IL_001a: ret       

  } // end of method zTest01::Foo

You’ll notice the ‘Foo’ code is distinctly different in both. This is because Unity recognized the code is really a bunch of constants and so pre-calculated the arithmetic of said constants. Where as the Visual Studio .net compiler did not optimize at all instead of allowing the code to remain mostly identical:

Visual Studio:

        static bool Foo()
        {
            double a = 3.14159274f / 7;

            float x = 3.14159274f / 7;
            double b = (double)x;

            return a != b;
        }

Unity:

  private static bool Foo()
  {
    return 0.448798963001796 != 0.448798954486847;
  }

It appears the Unity compiler favors its internal double floating point implementation rather than the .net implementation.

It also appears that it did the double to float back to double truncation for ‘b’, but directly double to double (ignore the ‘F’ on the constant) for ‘a’. (note that a, or left, is more accurate than b, or right. This says to me that b was truncated, and then coerced back into a double by the compiler)

This isn’t a bug… since the floating point standard does not require the implementation to be accurate from machine to machine, runtime to runtime. It’s the inherent design of floats… favoring speed/efficiency over reproducibility.

This is something you must always be aware of when dealing with floats.

If you need better accuracy using a fixed-point numeric type. Or the built in ‘decimal’ type.

Though still compiler differences may still arise like this here. Which again, is NOT a bug. There is no rule saying compilers must produce identical machine code (or intermediate code in the case of CIL).

3 Likes

Now… if you want to insist it’s a bug.

Then we’re going to get into semantics on a pedantic level.

And in the end… it doesn’t matter.

Cause it’s a bug that would be considered acceptable by both Unity and Microsoft, and thusly will not be changed (well it could change, but it won’t be changed to remedy your perceived issue… rather just because compilers change over time). And instead should just be something you as a developer shoulld be aware of. As it falls under the whole “never trust a float to be 100% accurate”.

So regardless of if you want to call it a bug or just implementation differences… the end result is the same. DO NOT TRUST FLOATS.

1 Like

Here is another puzzle. It’s for Visual Studio, I haven’t tested it in Unity.

using System;

class Program
{
    private static float sa, sb;

    public static void Main()
    {
        Console.WriteLine(Multiply(10f, .1f) == Multiply(10f, .1f));

        var la = Multiply(10f, .1f);
        var lb = Multiply(10f, .1f);
        Console.WriteLine(la == lb);

        sa = Multiply(10f, .1f);
        sb = Multiply(10f, .1f);
        Console.WriteLine(sa == sb);

        Console.ReadLine();
    }

    private static float Multiply(float x, float y) => x * y;
}

The output depends on the configuration:

  1. Net Framework 3.5, x86, release build: False, True, True
  2. Net Framework 4.6, x86, release build: False, False, True
  3. x64 or debug build or doubles instead of floats: True, True, True

Can anyone come up with an explanation? There’s a hint inside.