Skip to content

Round-tripping int.MaxValue through float differs between MonoVM & CoreCLR #119821

Closed as duplicate

Description

@jonpryor

Description

Trying to round-trip int.MaxValue through System.Single results in different behavior between MonoVM and CoreCLR.

Consider this C# fragment:

int a = int.MaxValue;
float f = a;
int b = (int)f;

Console.WriteLine($"{a:x} -> {f} -> {b:x}");

Under CoreCLR via dotnet run, it prints:

7fffffff -> 2.1474836E+09 -> 7fffffff

Under MonoVM via mono app.exe, it prints:

7fffffff -> 2.147484E+09 -> 80000000

i.e. the value does not round-trip on MonoVM.


Part of the problem is that System.Single cannot fully represent int.MaxValue; System.Double is needed to do that. As such, the representation for ((float) 7fffffff) and (float) 80000000) is identical!

float x = 0x7fffffff;
float y = 0x80000000;
Console.WriteLine($"{x} == {y} ? {x == y}");
var sx = string.Join(" ", BitConverter.GetBytes(x).Select(b => b.ToString("x2")));
var sy = string.Join(" ", BitConverter.GetBytes(y).Select(b => b.ToString("x2")));
Console.WriteLine($"sx={sx}");
Console.WriteLine($"sy={sy}");

With CoreCLR, the above prints:

2.1474836E+09 == 2.1474836E+09 ? True
sx=00 00 00 4f
sy=00 00 00 4f

With MonoVM, the above prints:

2.147484E+09 == 2.147484E+09 ? True
sx=00 00 00 4f
sy=00 00 00 4f

Here we see that the BitConverter.GetBytes() values are identical across CoreCLR and MonoVM, while the .ToString() output differs ("2.1474836E+09" vs "2.147484E+09").

Reproduction Steps

Please see the following program:

using System;
using System.Linq;

class App
{
    public static void Main()
    {
        int a = int.MaxValue;
        float f = a;
        int b = (int)f;

        Console.WriteLine($"{a:x} -> {f} -> {b:x}");

        float x = 0x7fffffff;
        float y = 0x80000000;
        Console.WriteLine($"{x} == {y} ? {x == y}");
        var sx = string.Join(" ", BitConverter.GetBytes(x).Select(b => b.ToString("x2")));
        var sy = string.Join(" ", BitConverter.GetBytes(y).Select(b => b.ToString("x2")));
        Console.WriteLine($"sx={sx}");
        Console.WriteLine($"sy={sy}");
    }
}

Expected behavior

Uncertain.

Should MonoVM and CoreCLR behave consistently here? This is something to debate.

Actual behavior

CoreCLR can round-trip int.MaxValue through float, but not MonoVM .

Regression?

No.

Known Workarounds

Don't Do That™.

Floating point is hard, mkay?

Configuration

No response

Other information

This difference was experienced while trying to run the unoplatform/uno unit tests in an Android+CoreCLR environment: unoplatform/uno#21477

The Given_ContentPresenter.When_Native_Host_Infinite_Measure_Bounds() started failing when CoreCLR was used.

This was eventually tracked to an interaction between AndroidSkiaNativeElementHostingExtension.MeasureNativeElement():

var availablePhysical = availableSize.LogicalToPhysicalPixels();
view.Measure((int)availablePhysical.Width, (int)availablePhysical.Height);

and ViewHelper.LogicalToPhysicalPixels():

if (double.IsPositiveInfinity(value) || value > MaxLogicalValue)
{
	return int.MaxValue;
}

availablePhysical is a Size structure with float members.

When one of the members is Infinite, then Uno replaces Infinite with int.MaxValue. However, as Size has float members, the value is not properly represented under MonoVM.

When MeasureNativeElement() then does (int) availablePhysical.Height, this converts the float back to an int.

Under MonoVM, this cast results in the value 0x80000000, which is equivalent to makeMeasureSpec(0, View.MeasureSpec.AT_MOST /* 0x80000000 */), which (eventually) results in a view.MeasuredHeight of 0.

Under CoreCLR, this cast results in the value 0x7fffffff, which is equivalent to makeMeasureSpec(0x00ffffff, UNSPECIFIED /* 0 */), which (eventually) results in a view.MeasuredHeight of 16777215.

Activity

  1. added
    needs-area-labelAn area label is needed to ensure this gets routed to the appropriate area owners
    on Sep 17, 2025
  2. added and removed
    needs-area-labelAn area label is needed to ensure this gets routed to the appropriate area owners
    on Sep 17, 2025
  3. dotnet-policy-service commented on Sep 17, 2025

    @dotnet-policy-service
    Contributor

    Tagging subscribers to this area: @dotnet/area-system-numerics
    See info in area-owners.md if you want to be subscribed.

  4. jkotas commented on Sep 17, 2025

    @jkotas
    Member

    Starting with .NET 9, floating point to integer conversions have defined saturating behavior on CoreCLR across all architectures: https://learn.microsoft.com/en-us/dotnet/core/compatibility/jit/9.0/fp-to-integer . It explains the behavior you are seeing on CoreCLR.

    This was not fixed on Mono. These conversions have undefined behavior in Mono. #100368 tracks fixing that.

    Resolving as duplicate of #100368

  5. locked and limited conversation to collaborators on Oct 18, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions