-1

I have Unity app and Desktop app that have shared C# code, and I've encountered an inconsistency with some lines in this code. For example:

float a = 2.34567890F, b = 1.23456782F;
double d = a + b;

In the second line:

  • Unity first converts a and b to doubles and then sums them
  • Desktop first sums them, and then converts to double

And that's yielding different results in d.

The question:

Is there way to prevent Unity from doing this (or make Desktop apps behave the same way)? Is there other way to workaround this inconsistency? (Of course, without checking each line and make all the explicit conversions to imitate Unity behavior)


Elaborated illustration

Consider this code:

using (System.IO.StreamWriter f = System.IO.File.CreateText(filename))
{
    float a = 2.34567890F;
    float b = 1.23456782F;

    double d1 = a + b; // sum and then convert
    f.WriteLine(BitConverter.ToString(BitConverter.GetBytes(d1)));

    double d2 = (double)a + (double)b; // convert and then sum
    f.WriteLine(BitConverter.ToString(BitConverter.GetBytes(d2)));
}

When I run it through Desktop Application (DotNet Core 3.1, on Windows), I'm getting different prints:

00-00-00-**40**-58-A4-0C-40
00-00-00-**50**-58-A4-0C-40

and this is understandable, due to the fact that:

  1. In the first line we do float-summation, and then conversion-to-double;
  2. While in the second we do 2 conversions-to-double, and then - double-summation.

However, in Unity Application (2019.4.11f1, on the same machine), I'm getting the second print - twice:

00-00-00-**50**-58-A4-0C-40
00-00-00-**50**-58-A4-0C-40

It seems that in both cases Unity firstly converts the 2 floats to double, and then performs double-summation.

Needless to say, there are many implicit-conversions in the code, and innocent phrases like Math.Sqrt(a+b) (when a,b are floats) return different values in Unity and Desktop, and down the road the results are chaotically-differ from each other.

Yaakov Shoham
  • 10,182
  • 7
  • 37
  • 45
  • I don't work with Unity, but it might be that it is using x87-math internally. Maybe there are some compile-options to change that. – chtz Nov 14 '21 at 16:44

1 Answers1

2

This is a difference between .NET virtual machines. If you look at the IL code generated by Unity and .NET Core they produce identical IL:

    IL_0001: ldc.r4 2.34567881
    IL_0006: stloc.0
    IL_0007: ldc.r4 1.23456776
    IL_000c: stloc.1
    IL_000d: ldloc.0
    IL_000e: ldloc.1
    IL_000f: add
    IL_0010: conv.r8

This suggests that the conversion happens after the add. So why is Unity behaving as if the conversion happens before the add? It's because Unity is using the Mono framework (and an old patched version of the Mono framework at that) which appears to perform the conversion during the add step. If you run the Unity generated .dll with .NET core you'll get the answer you expect:

00-00-00-40-58-A4-0C-40
00-00-00-50-58-A4-0C-40

However, if you run it using the Mono bundled with Unity (both 4.x and .NET Standard 2.0) you'll get the different answer:

00-00-00-50-58-A4-0C-40
00-00-00-50-58-A4-0C-40

For even more fun, you can switch from Mono to IL2CPP in Unity and then you'll get an answer that matches .NET Core! (Note: You'll need to actually build the app to see this. Running in-editor will continue to use the Mono framework)

00-00-00-40-58-A4-0C-40
00-00-00-50-58-A4-0C-40

So even Unity is not consistent...

Giawa
  • 1,281
  • 1
  • 10
  • 21
  • Thanks. Sounds awful. :) And after reading Eric Lipert comments here https://stackoverflow.com/questions/52953608/ and his answer here https://stackoverflow.com/questions/14864238/ it seems that theoretically it's lost battle when you switch frameworks. I'm wondering, in case you know you'll have just 2 specific frameworks, how much work it'll be to find all the problematic cases and write the code in a way that will always yield the same result. – Yaakov Shoham Nov 15 '21 at 11:30
  • 1
    It looks like his answer suggests that Unity/mono are absolutely fine to use higher precision for add, and the fact that .net core and il2cpp do not do that is also fine. I guess his final comment sort of answers your question about yielding the same result - you'll need to use integers! – Giawa Nov 15 '21 at 17:38