Repository navigation
Round-tripping int.MaxValue through float differs between MonoVM & CoreCLR #119821
Description
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
chore: build & run SamplesApp.Skia.netcoremobile with CoreCLR on Android
dotnet-policy-service commentedon Sep 17, 2025
Tagging subscribers to this area: @dotnet/area-system-numerics
See info in area-owners.md if you want to be subscribed.
jkotas commentedon Sep 17, 2025
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
chore: build & run SamplesApp.Skia.netcoremobile with CoreCLR on Android