Search Unity

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

Discussion in 'Scripting' started by iperov, Aug 22, 2017.

  1. iperov

    iperov

    Joined:
    Dec 13, 2015
    Posts:
    36
    Unity:


    MSVS:

    so math error will accumulating

    how to deal with it ?
     
  2. iperov

    iperov

    Joined:
    Dec 13, 2015
    Posts:
    36
    and how to explain this?

    Unity:

    but in MSVC no problem
     
  3. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    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?
     
  4. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    Huh?

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

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
  6. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
  7. iperov

    iperov

    Joined:
    Dec 13, 2015
    Posts:
    36
    what rounding errors in this case ?

     
  8. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    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.
     
  9. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    What the hell? Use code tags. Have some respect for forum users or I'll lock this thread.

    Never use screenshots for code.

     
    Zenix likes this.
  10. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
  11. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    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.
     
  12. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    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.
     
  13. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    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.
     
  14. iperov

    iperov

    Joined:
    Dec 13, 2015
    Posts:
    36
    I post screenshot as proof of breakpoint.

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

     
  15. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
  16. iperov

    iperov

    Joined:
    Dec 13, 2015
    Posts:
    36
    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.
     
  17. BlackPete

    BlackPete

    Joined:
    Nov 16, 2016
    Posts:
    970
    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.
     
  18. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    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:
    Code (csharp):
    1.  
    2. using System;
    3. using System.Collections.Generic;
    4. using System.Linq;
    5. using System.Text;
    6. using System.Threading.Tasks;
    7.  
    8. namespace Console01
    9. {
    10.     class Program
    11.     {
    12.         static void Main(string[] args)
    13.         {
    14.             if(Foo())
    15.             {
    16.                 Console.WriteLine("NOT EQUAL");
    17.             }
    18.  
    19.             Console.ReadLine();
    20.         }
    21.  
    22.         static bool Foo()
    23.         {
    24.             double a = 3.14159274f / 7;
    25.  
    26.             float x = 3.14159274f / 7;
    27.             double b = (double)x;
    28.  
    29.             return a != b;
    30.         }
    31.     }
    32. }
    33.  
    Unity:
    Code (csharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6. public class zTest01 : MonoBehaviour {
    7.  
    8.     // Use this for initialization
    9.     void Start () {
    10.         if (Foo())
    11.         {
    12.             Debug.Log("NOT EQUAL");
    13.         }
    14.     }
    15.  
    16.  
    17.     static bool Foo()
    18.     {
    19.         double a = 3.14159274f / 7;
    20.  
    21.         float x = 3.14159274f / 7;
    22.         double b = (double)x;
    23.  
    24.         return a != b;
    25.     }
    26.  
    27. }
    28.  
    Now, I built both, and then decompiled them using Dotpeek:
    https://www.jetbrains.com/decompiler/

    The CIL for just the Foo method:...

    Visual Studio:
    Code (csharp):
    1.  
    2.   .method private hidebysig static bool
    3.     Foo() cil managed
    4.   {
    5.     .maxstack 2
    6.     .locals init (
    7.       [0] float64 a,
    8.       [1] float32 x,
    9.       [2] float64 b,
    10.       [3] bool V_3
    11.     )
    12.  
    13.     // [22 9 - 22 10]
    14.     IL_0000: nop      
    15.  
    16.     // [23 13 - 23 40]
    17.     IL_0001: ldc.r8       0.448798954486847
    18.     IL_000a: stloc.0      // a
    19.  
    20.     // [25 13 - 25 39]
    21.     IL_000b: ldc.r4       0.448799
    22.     IL_0010: stloc.1      // x
    23.  
    24.     // [26 13 - 26 34]
    25.     IL_0011: ldloc.1      // x
    26.     IL_0012: conv.r8  
    27.     IL_0013: stloc.2      // b
    28.  
    29.     // [28 13 - 28 27]
    30.     IL_0014: ldloc.0      // a
    31.     IL_0015: ldloc.2      // b
    32.     IL_0016: ceq      
    33.     IL_0018: ldc.i4.0
    34.     IL_0019: ceq      
    35.     IL_001b: stloc.3      // V_3
    36.     IL_001c: br.s         IL_001e
    37.  
    38.     // [29 9 - 29 10]
    39.     IL_001e: ldloc.3      // V_3
    40.     IL_001f: ret      
    41.  
    42.   } // end of method Program::Foo
    43.  
    Unity:
    Code (csharp):
    1.  
    2.   .method private hidebysig static bool
    3.     Foo() cil managed
    4.   {
    5.     .maxstack 2
    6.     .locals init (
    7.       [0] float64 V_0,
    8.       [1] float32 V_1,
    9.       [2] float64 V_2
    10.     )
    11.  
    12.     // [20 5 - 20 51]
    13.     IL_0000: ldc.r8       0.448798963001796
    14.     IL_0009: stloc.0      // V_0
    15.     IL_000a: ldc.r4       0.448799
    16.     IL_000f: stloc.1      // V_1
    17.     IL_0010: ldloc.1      // V_1
    18.     IL_0011: conv.r8  
    19.     IL_0012: stloc.2      // V_2
    20.     IL_0013: ldloc.0      // V_0
    21.     IL_0014: ldloc.2      // V_2
    22.     IL_0015: ceq      
    23.     IL_0017: ldc.i4.0
    24.     IL_0018: ceq      
    25.     IL_001a: ret      
    26.  
    27.   } // end of method zTest01::Foo
    28.  
    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:
    Code (csharp):
    1.  
    2.         static bool Foo()
    3.         {
    4.             double a = 3.14159274f / 7;
    5.  
    6.             float x = 3.14159274f / 7;
    7.             double b = (double)x;
    8.  
    9.             return a != b;
    10.         }
    11.  
    Unity:
    Code (csharp):
    1.  
    2.   private static bool Foo()
    3.   {
    4.     return 0.448798963001796 != 0.448798954486847;
    5.   }
    6.  
    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).
     
    Last edited: Aug 23, 2017
    PraetorBlue, Kiwasi and KelsoMRK like this.
  19. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    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.
     
    Kiwasi likes this.
  20. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    Here is another puzzle. It's for Visual Studio, I haven't tested it in Unity.
    Code (CSharp):
    1. using System;
    2.  
    3. class Program
    4. {
    5.     private static float sa, sb;
    6.  
    7.     public static void Main()
    8.     {
    9.         Console.WriteLine(Multiply(10f, .1f) == Multiply(10f, .1f));
    10.  
    11.         var la = Multiply(10f, .1f);
    12.         var lb = Multiply(10f, .1f);
    13.         Console.WriteLine(la == lb);
    14.  
    15.         sa = Multiply(10f, .1f);
    16.         sb = Multiply(10f, .1f);
    17.         Console.WriteLine(sa == sb);
    18.  
    19.         Console.ReadLine();
    20.     }
    21.  
    22.     private static float Multiply(float x, float y) => x * y;
    23. }
    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.
     
  21. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,147
    Microsoft Visual C++.
     
  22. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    I would like to agree, but contextually it doesn't exactly make sense.
     
  23. BlackPete

    BlackPete

    Joined:
    Nov 16, 2016
    Posts:
    970
    MSVC was pretty much "the" name for Visual Studio before C# pretty much took over in popularity. Now it's just MSVS. I'm sure the OP was just using the two terms interchangeably.
     
  24. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    The point I was making by my chain of questions:
    Is that the question was vague, and that acronyms/abbreviations don't help, especially if they interchange MSVS and MSVC to mean the same thing.

    Essentially I was trying to get OP to describe their question in better detail. Not actually find out what random thing the acronym meant.
     
  25. RomanGR8

    RomanGR8

    Joined:
    Apr 26, 2020
    Posts:
    1
    I believe below two links offer some explanation to why the results are different.

    https://tirania.org/blog/archive/2018/Apr-11.html
    https://github.com/mono/mono/issues/7522

    Unity uses Mono and Mono is performing all 32-bit float calculations as it they were 64-bit floats and storing result as 32-bit float. Apart from the sometimes unexpected results - it is also slower.

    MSVS (Microsoft Visual Studio) uses .NET framework (or .NET Core, whatever you select) and it uses 32-bit float calculations 32-bit float variables.