FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Math.Min overload resolution narrows Int64/UInt64 to Int32 depending on argument order · Issue #28377 · PowerShell/PowerShell · GitHub

Repository navigation

Math.Min overload resolution narrows Int64/UInt64 to Int32 depending on argument order #28377

Open

Description

AI disclosure: GPT 6.1 Sol detected and reproduced this behavior and prepared this report with the user's authorization. If maintainers consider this report AI slop, please feel free to close or delete this issue.

Prerequisites

Steps to reproduce

Reproduced on the latest stable release, PowerShell 7.6.9, using the official Windows x64 portable archive with -NoProfile -NonInteractive (.NET 10.0.14). This first appeared in a bounded-buffer calculation with a 64-bit remaining byte count, but the reproducer performs only numeric operations.

Evaluate each expression independently:

# Both of these attempt to convert the second operand to Int32 and fail:
[Math]::Min([int]4194304, [long]30784094208)
[Math]::Min([int]4194304, [UInt64]30784094208)

# Reversing the operands succeeds:
[Math]::Min([long]30784094208, [int]4194304)
[Math]::Min([UInt64]30784094208, [int]4194304)

# Explicitly matching operand types also succeeds:
[Math]::Min([long]4194304, [long]30784094208)
[Math]::Min([UInt64]4194304, [UInt64]30784094208)

# Boundary: the first succeeds; the second fails although both minima are 4194304:
[Math]::Min([int]4194304, [UInt64]2147483647)
[Math]::Min([int]4194304, [UInt64]2147483648)

The signed Int32/Int64 case has a lossless Int64 overload available for both operands. The report concerns PowerShell's method overload selection, not the calculation within .NET Math.Min.

I am not claiming a new regression or proposing an unreviewed breaking change to numeric conversion rules. If this is intentional binding precedence, please classify accordingly and clarify whether an enhancement or more specific documentation is appropriate. I have read the existing guidance recommending exact argument types. A similar historical downstream report is MVKozlov/GMGoogleDrive#1.

Expected behavior

Each mixed-type call should return 4194304 using a suitable 64-bit overload, or consistently report ambiguity. In particular, the Int32/Int64 pair has a lossless common Int64 overload. Reversing the operands should not turn a successful minimum calculation into an overflow.

Actual behavior

PowerShell 7.6.9 results:

Int32, Int64       -> MethodArgumentConversionInvalidCastArgument (val2 -> System.Int32)
Int64, Int32       -> 4194304 (System.Int64)
Int32, UInt64      -> MethodArgumentConversionInvalidCastArgument (val2 -> System.Int32)
UInt64, Int32      -> 4194304 (System.UInt64)
Int64, Int64       -> 4194304 (System.Int64)
UInt64, UInt64     -> 4194304 (System.UInt64)
Int32, UInt64(Int32.MaxValue)     -> 4194304 (System.Int32)
Int32, UInt64(Int32.MaxValue + 1) -> MethodArgumentConversionInvalidCastArgument

Error message:
Cannot convert argument "val2", with value: "30784094208", for "Min" to type "System.Int32": "Cannot convert value "30784094208" to type "System.Int32". Error: "Value was either too large or too small for an Int32.""

Error details

Excerpt from Get-Error for [math]::Min([int]4194304, [UInt64]30784094208):

Type                  : System.Management.Automation.MethodException
FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument
InnerException        : System.Management.Automation.PSInvalidCastException
InnerException        : System.OverflowException
Message               : Value was either too large or too small for an Int32.

StackTrace :
   at System.Convert.ThrowInt32OverflowException()
   at System.Convert.ToInt32(UInt64 value)
   at System.Convert.ChangeType(Object value, Type conversionType, IFormatProvider provider)
   at System.Management.Automation.LanguagePrimitives.ConvertNumeric(Object valueToConvert, Type resultType, Boolean recursion, PSObject originalValueToConvert, IFormatProvider formatProvider, TypeTable backupTable)
   at CallSite.Target(Closure, CallSite, Type, Int32, UInt64)

Environment data

Name                      Value
----                      -----
PSVersion                 7.6.9
PSEdition                 Core
GitCommitId               7.6.9
OS                        Microsoft Windows 10.0.26300
Platform                  Win32NT
PSCompatibleVersions      {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion 2.4
SerializationVersion      1.1.0.1
WSManStackVersion         3.0

Visuals

No response

Activity

  1. 237dmitry commented on Nov 7, 2026

    As I understand it, operands must be of the same type for a comparison to take place. If they differ, the second operand attempts to adopt the type of the first. If this is not possible, for instance, because the value exceeds the maximum limit for that type, an error occurs.

  2. doctordns commented on Nov 7, 2026

    Collaborator

    I am unsure whether this repo has any AI policy. Speaking personally, I'd prefer to keep PowerShell Ai-slop free. But given who owns PowerShell, I suspect thst boat has sailed.

  3. mklement0 commented on Nov 8, 2026

    Contributor

    rhubarb-geek-nz:

    A reflection-based investigation using .GetMethod() with an array of parameter types isn't a good test, as it relies on .NET's overload selection, which is generally more restrictive than PowerShell's (e.g., [int]::Parse([char] '9') works fine in PowerShell, but the equivalent int.Parse('9') in C# fails) - that said, avoiding differing behaviors is obviously desirable in cases where both selection algorithms do succeed - see next section.

    Also, at least in C#:

    • In actual calls, the Single (aka float) overload is seemingly never implicitly chosen with integer-type operands - e.g., int a = 1; ulong b = 2; Math.Min(a, b).GetType() throws an ambiguous-overloads exception (The call is ambiguous between the following methods or properties: 'System.Math.Min(decimal, decimal)' and 'System.Math.Min(float, float)')

    • Only if the smaller integer operand type can be coerced to a larger integer one does the call succeed; e.g. Math.Min(1, 2u).GetType() works and returns uint (though, if the operands aren't literals, i.e. their values aren't known until runtime, long is returned, as only it is guaranteed to accommodate all int and uint values; e.g., int a = 1; uint b = 2; Math.Min(a, b).GetType()), but Math.Min(-1, 2ul).GetType() again fails with the ambiguous-overloads exception (there is no signed integer type larger than ulong).

    In general, however: C# / .NET:

    • does pick a larger-integer overload, if available, even when the second operand is larger, e.g. sbyte a = 1; ushort b = 2; Math.Min(a, b)
    • of necessity picks an overload with a signed larger integer type if at least one of the operands is of a signed type; e.g. the above returns an int - larger than both operand types, and signed.

    In PowerShell:

    • While when it comes to operators, it is typically the LHS operand that locks in the type of the operation, as 237dmitry notes, this isn't always true, notably not with numeric operators, where the RHS operand can force the LHS to a larger type ; e.g., 1 + 2147483648l works just fine and returns a long result.

    • It is therefore understandable to expect something like [Math]::Min(1, 2147483648l) to work analogously, i.e. to implicitly work like [Math]::Min([long] 1, 2147483648l); however, the former fails - whereas it succeeds in C#.

    I cannot speak to whether this could be made to work in a predictable, unambiguous way that doesn't break backward compatibility.

    Also note:

    • The picture is complicated by the fact that, in .NET method calls from PowerShell, casts are explicit signals that guide overload resolution (selection); for instance, [Math]::Min(1, 2147483648l) fails, as discussed, but the seemingly equivalent [Math]::Min(1, [long] 2147483648) succeeds, because the [long] cast makes PowerShell select the Math.Min(long, long) overload.

    • It seems that the first explicit cast takes precedence, so that the again seemingly equivalent [Math]::Min([int] 1, [long] 2147483648) again fails, as the Math.Min(int, int) overload is now chosen.

    • However, as an effective solution / workaround, you can place a cast to the potentially largest type among the operands before the first operand (only). However, note that there is no [bigint] overload; [long] is a candidate (or [ulong] if you know all operands to be nonnegative), or, to accommodate even larger numbers, [decimal] (beyond that, [double] is an option, but invariably results in loss of accuracy); e.g., [Math]::Min([long] 1, 2147483648), [Math]::Min([ulong] 1, 9223372036854775808), [Math]::Min([decimal] 1, 18446744073709551616)

  4. iRon7 commented on Nov 10, 2026

    A similar behavior can be seen with [Math]::max and [int] / [Double] combinations:
    (it doesn't error but the result is somehow unexpected)

    [Math]::max(1.4, 1) # yields 1.4
    [Math]::max(1, 1.4) # yields 1
  5. Tragen commented on Nov 10, 2026

    I had a similar bug with bigint and it was closed
    #21341
    C# gives you warning, PS failes silently or gives wrong results.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs-TriageThe issue is new and needs to be triaged by a work group.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions


      Back | FazBrowse Home | New Git URL