From 2da1f9fc0cbb508adcf96c048e753745c3d8ce1b Mon Sep 17 00:00:00 2001 From: KirtiRamchandani Date: Sun, 14 Jun 2026 09:17:02 +0000 Subject: [PATCH] Promote Int128/UInt128 arithmetic overflow to Double Route Int128 and UInt128 through the numeric operations pipeline with dedicated overflow handling, matching Int64 behavior instead of relying on native wrapping operators. Fixes #26677 --- .../engine/LanguagePrimitives.cs | 12 +- .../engine/runtime/Binding/Binders.cs | 10 ++ .../engine/runtime/Operations/NumericOps.cs | 155 ++++++++++++++++++ .../utils/ExtensionMethods.cs | 10 +- .../Operators/ArithmeticOperators.Tests.ps1 | 29 ++++ 5 files changed, 207 insertions(+), 9 deletions(-) create mode 100644 test/powershell/Language/Operators/ArithmeticOperators.Tests.ps1 diff --git a/src/System.Management.Automation/engine/LanguagePrimitives.cs b/src/System.Management.Automation/engine/LanguagePrimitives.cs index 13d8f66e5e9..87a1c06e742 100644 --- a/src/System.Management.Automation/engine/LanguagePrimitives.cs +++ b/src/System.Management.Automation/engine/LanguagePrimitives.cs @@ -4446,22 +4446,22 @@ internal static ConversionRank GetConversionRank(Type fromType, Type toType) } private static readonly Type[] s_numericTypes = new Type[] { - typeof(Int16), typeof(Int32), typeof(Int64), - typeof(UInt16), typeof(UInt32), typeof(UInt64), + typeof(Int16), typeof(Int32), typeof(Int64), typeof(Int128), + typeof(UInt16), typeof(UInt32), typeof(UInt64), typeof(UInt128), typeof(sbyte), typeof(byte), typeof(Single), typeof(double), typeof(decimal), typeof(BigInteger) }; private static readonly Type[] s_integerTypes = new Type[] { - typeof(Int16), typeof(Int32), typeof(Int64), - typeof(UInt16), typeof(UInt32), typeof(UInt64), + typeof(Int16), typeof(Int32), typeof(Int64), typeof(Int128), + typeof(UInt16), typeof(UInt32), typeof(UInt64), typeof(UInt128), typeof(sbyte), typeof(byte) }; // Do not reorder the elements of these arrays, we depend on them being ordered by increasing size. - private static readonly Type[] s_signedIntegerTypes = new Type[] { typeof(sbyte), typeof(Int16), typeof(Int32), typeof(Int64) }; - private static readonly Type[] s_unsignedIntegerTypes = new Type[] { typeof(byte), typeof(UInt16), typeof(UInt32), typeof(UInt64) }; + private static readonly Type[] s_signedIntegerTypes = new Type[] { typeof(sbyte), typeof(Int16), typeof(Int32), typeof(Int64), typeof(Int128) }; + private static readonly Type[] s_unsignedIntegerTypes = new Type[] { typeof(byte), typeof(UInt16), typeof(UInt32), typeof(UInt64), typeof(UInt128) }; private static readonly Type[] s_realTypes = new Type[] { typeof(Single), typeof(double), typeof(decimal) }; diff --git a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs index 027c4886380..05173a6be3c 100644 --- a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs +++ b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs @@ -2444,6 +2444,16 @@ private DynamicMetaObject BinaryNumericOp(string methodName, DynamicMetaObject t argType = typeof(ulong); } } + else if (target.LimitType.IsInt128Type() || arg.LimitType.IsInt128Type()) + { + opImplType = typeof(Int128Ops); + argType = typeof(Int128); + } + else if (target.LimitType.IsUInt128Type() || arg.LimitType.IsUInt128Type()) + { + opImplType = typeof(UInt128Ops); + argType = typeof(UInt128); + } else if (opTypeCode == TypeCode.Decimal) { if (methodName.StartsWith("Compare", StringComparison.Ordinal)) diff --git a/src/System.Management.Automation/engine/runtime/Operations/NumericOps.cs b/src/System.Management.Automation/engine/runtime/Operations/NumericOps.cs index f5670899c23..8a9a997600c 100644 --- a/src/System.Management.Automation/engine/runtime/Operations/NumericOps.cs +++ b/src/System.Management.Automation/engine/runtime/Operations/NumericOps.cs @@ -394,6 +394,161 @@ internal static object Remainder(ulong lhs, ulong rhs) internal static object CompareGe(ulong lhs, ulong rhs) { return (lhs >= rhs) ? Boxed.True : Boxed.False; } } + internal static class Int128Ops + { + internal static object Add(Int128 lhs, Int128 rhs) + { + System.Numerics.BigInteger biResult = (System.Numerics.BigInteger)lhs + (System.Numerics.BigInteger)rhs; + if (biResult >= Int128.MinValue && biResult <= Int128.MaxValue) + { + return (Int128)biResult; + } + + return (double)biResult; + } + + internal static object Sub(Int128 lhs, Int128 rhs) + { + System.Numerics.BigInteger biResult = (System.Numerics.BigInteger)lhs - (System.Numerics.BigInteger)rhs; + if (biResult >= Int128.MinValue && biResult <= Int128.MaxValue) + { + return (Int128)biResult; + } + + return (double)biResult; + } + + internal static object Multiply(Int128 lhs, Int128 rhs) + { + System.Numerics.BigInteger biResult = (System.Numerics.BigInteger)lhs * (System.Numerics.BigInteger)rhs; + if (biResult >= Int128.MinValue && biResult <= Int128.MaxValue) + { + return (Int128)biResult; + } + + return (double)biResult; + } + + internal static object Divide(Int128 lhs, Int128 rhs) + { + if (rhs == 0) + { + DivideByZeroException dbze = new DivideByZeroException(); + throw new RuntimeException(dbze.Message, dbze); + } + + if (lhs == Int128.MinValue && rhs == -1) + { + return (double)lhs / (double)rhs; + } + + if ((lhs % rhs) == 0) + { + return lhs / rhs; + } + + return (double)lhs / (double)rhs; + } + + internal static object Remainder(Int128 lhs, Int128 rhs) + { + if (rhs == 0) + { + DivideByZeroException dbze = new DivideByZeroException(); + throw new RuntimeException(dbze.Message, dbze); + } + + return lhs % rhs; + } + + internal static object CompareEq(Int128 lhs, Int128 rhs) { return (lhs == rhs) ? Boxed.True : Boxed.False; } + + internal static object CompareNe(Int128 lhs, Int128 rhs) { return (lhs != rhs) ? Boxed.True : Boxed.False; } + + internal static object CompareLt(Int128 lhs, Int128 rhs) { return (lhs < rhs) ? Boxed.True : Boxed.False; } + + internal static object CompareLe(Int128 lhs, Int128 rhs) { return (lhs <= rhs) ? Boxed.True : Boxed.False; } + + internal static object CompareGt(Int128 lhs, Int128 rhs) { return (lhs > rhs) ? Boxed.True : Boxed.False; } + + internal static object CompareGe(Int128 lhs, Int128 rhs) { return (lhs >= rhs) ? Boxed.True : Boxed.False; } + } + + internal static class UInt128Ops + { + internal static object Add(UInt128 lhs, UInt128 rhs) + { + System.Numerics.BigInteger biResult = (System.Numerics.BigInteger)lhs + (System.Numerics.BigInteger)rhs; + if (biResult >= UInt128.MinValue && biResult <= UInt128.MaxValue) + { + return (UInt128)biResult; + } + + return (double)biResult; + } + + internal static object Sub(UInt128 lhs, UInt128 rhs) + { + System.Numerics.BigInteger biResult = (System.Numerics.BigInteger)lhs - (System.Numerics.BigInteger)rhs; + if (biResult >= UInt128.MinValue && biResult <= UInt128.MaxValue) + { + return (UInt128)biResult; + } + + return (double)biResult; + } + + internal static object Multiply(UInt128 lhs, UInt128 rhs) + { + System.Numerics.BigInteger biResult = (System.Numerics.BigInteger)lhs * (System.Numerics.BigInteger)rhs; + if (biResult >= UInt128.MinValue && biResult <= UInt128.MaxValue) + { + return (UInt128)biResult; + } + + return (double)biResult; + } + + internal static object Divide(UInt128 lhs, UInt128 rhs) + { + if (rhs == 0) + { + DivideByZeroException dbze = new DivideByZeroException(); + throw new RuntimeException(dbze.Message, dbze); + } + + if ((lhs % rhs) == 0) + { + return lhs / rhs; + } + + return (double)lhs / (double)rhs; + } + + internal static object Remainder(UInt128 lhs, UInt128 rhs) + { + if (rhs == 0) + { + DivideByZeroException dbze = new DivideByZeroException(); + throw new RuntimeException(dbze.Message, dbze); + } + + return lhs % rhs; + } + + internal static object CompareEq(UInt128 lhs, UInt128 rhs) { return (lhs == rhs) ? Boxed.True : Boxed.False; } + + internal static object CompareNe(UInt128 lhs, UInt128 rhs) { return (lhs != rhs) ? Boxed.True : Boxed.False; } + + internal static object CompareLt(UInt128 lhs, UInt128 rhs) { return (lhs < rhs) ? Boxed.True : Boxed.False; } + + internal static object CompareLe(UInt128 lhs, UInt128 rhs) { return (lhs <= rhs) ? Boxed.True : Boxed.False; } + + internal static object CompareGt(UInt128 lhs, UInt128 rhs) { return (lhs > rhs) ? Boxed.True : Boxed.False; } + + internal static object CompareGe(UInt128 lhs, UInt128 rhs) { return (lhs >= rhs) ? Boxed.True : Boxed.False; } + } + internal static class DecimalOps { internal static object Add(decimal lhs, decimal rhs) diff --git a/src/System.Management.Automation/utils/ExtensionMethods.cs b/src/System.Management.Automation/utils/ExtensionMethods.cs index f3dc94c047b..4ce3d2a67cf 100644 --- a/src/System.Management.Automation/utils/ExtensionMethods.cs +++ b/src/System.Management.Automation/utils/ExtensionMethods.cs @@ -85,12 +85,12 @@ internal static bool HasDefaultCtor(this Type type) internal static bool IsNumeric(this Type type) { - return LanguagePrimitives.IsNumeric(LanguagePrimitives.GetTypeCode(type)); + return LanguagePrimitives.IsNumeric(LanguagePrimitives.GetTypeCode(type)) || type.IsInt128Type() || type.IsUInt128Type(); } internal static bool IsNumericOrPrimitive(this Type type) { - return type.IsPrimitive || LanguagePrimitives.IsNumeric(LanguagePrimitives.GetTypeCode(type)); + return type.IsPrimitive || type.IsInt128Type() || type.IsUInt128Type() || LanguagePrimitives.IsNumeric(LanguagePrimitives.GetTypeCode(type)); } internal static bool IsSafePrimitive(this Type type) @@ -105,9 +105,13 @@ internal static bool IsFloating(this Type type) internal static bool IsInteger(this Type type) { - return LanguagePrimitives.IsInteger(LanguagePrimitives.GetTypeCode(type)); + return LanguagePrimitives.IsInteger(LanguagePrimitives.GetTypeCode(type)) || type.IsInt128Type() || type.IsUInt128Type(); } + internal static bool IsInt128Type(this Type type) => type == typeof(Int128); + + internal static bool IsUInt128Type(this Type type) => type == typeof(UInt128); + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static TypeCode GetTypeCode(this Type type) { diff --git a/test/powershell/Language/Operators/ArithmeticOperators.Tests.ps1 b/test/powershell/Language/Operators/ArithmeticOperators.Tests.ps1 new file mode 100644 index 00000000000..6a463b9c71c --- /dev/null +++ b/test/powershell/Language/Operators/ArithmeticOperators.Tests.ps1 @@ -0,0 +1,29 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe "Arithmetic overflow promotion" -Tags "CI","RequireAdminOnWindows" { + BeforeAll { + $IsSkipped = (-not $IsCoreCLR) -or ($PSVersionTable.PSVersion -lt [version]'7.4.0') + } + + It "Promotes Int64 overflow to Double" -Skip:$IsSkipped { + $result = [Int64]::MaxValue + [Int64]::MaxValue + $result.GetType().Name | Should -Be 'Double' + } + + It "Promotes Int128 overflow to Double" -Skip:$IsSkipped { + $result = [Int128]::MaxValue + [Int128]::MaxValue + $result.GetType().Name | Should -Be 'Double' + } + + It "Keeps non-overflow Int128 addition in Int128" -Skip:$IsSkipped { + $result = [Int128]1 + [Int128]2 + $result.GetType().Name | Should -Be 'Int128' + $result | Should -Be ([Int128]3) + } + + It "Promotes UInt128 overflow to Double" -Skip:$IsSkipped { + $result = [UInt128]::MaxValue + [UInt128]::MaxValue + $result.GetType().Name | Should -Be 'Double' + } +}