Floating-point to integer conversions should saturate on overflow
- Dominant language
- C#
- Stars
- 20.7k
- Forks
- 4.3k
- PR merge metrics
- PR metrics pending
Description
### Current Behavior
The result of converting from a floating-point type to an integral type when the floating-point value is not representable in the target integer type is currently considered "unspecified". The Roslyn compiler currently normalizes this to be `0` across all platforms and architectures for determinism purposes (https://github.com/dotnet/roslyn/pull/37772).
### References
This is as per the language spec (https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/conversions.md):
> * For a conversion from float or double to an integral type, the processing depends on the overflow-checking context ([§12.8.19](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12819-the-checked-and-unchecked-operators)) in which the conversion takes place:
> * In a checked context, the conversion proceeds as follows:
> * If the value of the operand is NaN or infinite, a System.OverflowException is thrown.
> * Otherwise, the source operand is rounded towards zero to the nearest integral value. If this integral value is within the range of the destination type then this value is the result of the conversion.
> * Otherwise, a System.OverflowException is thrown.
> * In an unchecked context, the conversion always succeeds, and proceeds as follows.
> * If the value of the operand is NaN or infinite, the result of the conversion is an unspecified value of the destination type.
> * Otherwise, the source operand is rounded towards zero to the nearest integral value. If this integral value is within the range of the destination type then this value is the result of the conversion.
> * Otherwise, the result of the conversion is an unspecified value of the destination type.
The runtime spec ([ECMA-335](https://ecma-international.org/publications-and-standards/standards/ecma-335/)) -- III.3.27 conv.:
> If overflow occurs converting a floating-point type to an integer, or if the floating-point value being converted to an integer is a NaN, the value returned is unspecified.
and the [IEEE 754](https://standards.ieee.org/ieee/754/6210/) (otherwise known as ISO/IEC 60559) specification (noting that for the IEEE 754 specification, there is allowed disabling of floating-point exceptions, which is how the runtime operates):
> When a NaN or infinite operand cannot be represented in the destination format and this cannot otherwise be indicated, the invalid operation exception shall be signaled. When a numeric operand would convert to an integer outside the range of the destination format, the invalid operation exception shall be signaled if this situation cannot otherwise be indicated.
### Problem
While the behavior is left up to the implementation by all these specifications, this has lead to problems for users that are targeting different platforms (x64 vs Arm64) and is incompatible with some targets that require a more stringent behavior (WASM).
Accordingly, the runtime has made steps towards normalizing the behavior itself (https://github.com/dotnet/runtime/issues/61885) and the behavior we landed on is not the same one Roslyn opted for. In particular, we do want `NaN -> 0`, but for `overflow` we want it to instead `saturate` (that is if the source is greater than the maximum value of the target type, it saturates to the maximum value of the target type; if it is less than the minimum, it saturates to the minimum. Thus `float.MaxValue -> int.MaxValue`, `float.MinValue -> int.MinValue`).
This decision was made based on the choices made by several other languages (such as Rust and Java), the behavior of more modern platforms (such as Arm64), and the required behavior for key platforms coming online (such as WASM).
### Recommended Change
Accordingly, it would be beneficial if the Roslyn compiler could move towards the same behavior so that on .NET 9+ users will see the same results across all target CPU architectures regardless of whether the operation is constant folded by Roslyn, constant folded by the JIT/AOT, or evaluated at runtime.
This would simply entail that `NaN -> 0` and overflow is done via saturation, as described above. If appropriate or helpful, the runtime can define a `RuntimeFeature` flag to help drive this behavior and indicate that the target is expected to saturate.
Contributor guide
Assessment
This issue has not been assessed yet.