How to convert to UInt64 from a string in Powershell? String-to-number conversion

Mark Allison picture Mark Allison · Dec 11, 2016 · Viewed 7.1k times · Source

Consider the following Powershell snippet:

[Uint64] $Memory = 1GB
[string] $MemoryFromString = "1GB"
[Uint64] $ConvertedMemory = [Convert]::ToUInt64($MemoryFromString)

The 3rd Line fails with:

Exception calling "ToUInt64" with "1" argument(s): "Input string was not in a correct format."
At line:1 char:1
+ [Uint64]$ConvertedMemory = [Convert]::ToUInt64($MemoryFromString)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : FormatException

If I check the contents of $Memory:

PS C:\> $Memory
1073741824

That works fine.

So, how do I convert the value "1GB" from a string to a UInt64 in Powershell?

Answer

mklement0 picture mklement0 · Dec 11, 2016

To complement Sean's helpful answer:

It is only the type constraint of your result variable ([uint64] $ConvertedMemory = ...) that ensures that ($MemoryFromString / 1) is converted to [uint64] ([System.UInt64]).

The result of expression $MemoryFromString / 1 is actually of type [int] ([System.Int32]):

PS> ('1gb' / 1).GetType().FullName
System.Int32

Therefore, to ensure that the expression by itself returns an [uint64] instance, you'd have to use a cast:

PS> ([uint64] ('1gb' / 1)).GetType().FullName
System.Int64

Note the required (...) around the calculation, as the [uint64] cast would otherwise apply to '1gb' only (and therefore fail).
Alternatively, ('1gb' / [uint64] 1) works too.

Note:

  • '1gb' - 0 would have worked too,
  • but not '1gb' * 1' (effectively a no-op) or '1gb' + 0 (results in string '1gb0'), because operators * and + with a string-typed LHS perform string operations (replication and concatenation, respectively).

Automatic string-to-number conversion and number literals in PowerShell:

When PowerShell performs implicit number conversion, including when performing mixed-numeric-type calculations and parsing number literals in source code, it conveniently auto-selects a numeric type that is "large" enough to hold the result.

In implicit string-to-number conversions, PowerShell conveniently recognizes the same formats as supported in number literals in source code:

  • number-base prefixes (for integers only): 0x for hexadecimal integers, and 0b for binary integers (PowerShell [Core] 7.0+)

  • number-type suffixes: L for [long] ([System.Int64]), and D for [decimal] ([System.Decimal]); e.g., '1L' - 0 yields a [long].
    Note that C# uses M instead of D and instead uses D to designate [System.Double]; also, C# supports several additional suffixes.

    • PowerShell [Core] 6.2+ now supports additional suffixes: Y ([sbyte]), UY ([byte]), S ([int16]), US ([uint16]), U ([uint32] or [uint64], on demand), and UL ([uint64]).

    • PowerShell [Core] 7.0+ additionally suports suffix n ([bigint])

    • You can keep an eye on future developments, if any, via the official help topic, about_Numeric_Literals.

  • floating-point representations such as 1.23 (decimal only); note that PowerShell only ever recognizes . as the decimal mark, irrespective of the current culture.

  • exponential notation (decimal only); e.g., '1.0e3' - 1 yields 999.

  • its own binary-multiplier suffixes, kb, mb, gb, tb, pb (for multipliers [math]::pow(2, 10) == 1024, [math]::pow(2, 20) == 1048576, ...); e.g., '1kb' - 1 yields 1023; note that theses suffixes are PowerShell-specific, so the .NET framework number-parsing methods do not recognize them.

The number-conversion rules are complex, but here are some key points:

This is based on my own experiments. Do tell me if I'm wrong.
Types are expressed by their PS type accelerators and map onto .NET types as follows:
[int] ... [System.Int32]
[long] ... [System.Int64]
[decimal] ... [System.Decimal]
[float] ... [System.Single]
[double] ... [System.Double]

  • PowerShell never auto-selects an unsigned integer type.

    • Note: In PowerShell [Core] 6.2+, you can use type suffix US, U or UL (see above) to force treatment as an unsigned type (positive number); e.g., 0xffffffffffffffffU
    • This can be unexpected with hexadecimal number literals; e.g., [uint32] 0xffffffff fails, because 0xffffffff is first - implicitly - converted to signed type [int32], which yields -1, which, as a signed value, cannot then be cast to unsigned type [uint32].
    • Workarounds:
      • Append L to force interpretation as an [int64] first, which results in expected positive value 4294967295, in which case the cast to [uint32] succeeds.
      • That technique doesn't work for values above 0x7fffffffffffffff ([long]::maxvalue), however, in which case you can use string conversion: [uint64] '0xffffffffffffffff'
  • PowerShell widens integer types as needed:

    • For decimal integer literals / strings, widening goes beyond integer types to [System.Decimal], and then [Double], as needed; e.g.:

      • (2147483648).GetType().Name yields Int64, because the value is [int32]::MaxValue + 1, and was therefore implicitly widened to [int64].

      • (9223372036854775808).GetType().Name yields Decimal, because the value is [int64]::MaxValue + 1, and was therefore implicitly widened to [decimal].

      • (79228162514264337593543950336).GetType().Name yields Double, because the value is [decimal]::MaxValue + 1, and was therefore implicitly widened to [double].

    • For hexadecimal (invariably integer) literals / strings, widening stops at [int64]:

      • (0x100000000).gettype().name yields Int64, because the value is [int32]::MaxValue + 1, and was therefore implicitly widened to [int64].

      • 0x10000000000000000, which is [int64]::MaxValue + 1, does not get promoted to [System.Decimal] due to being hexadecimal and interpretation as a number therefore fails.

    • Note: The above rules apply to individual literals / strings, but widening in expressions may result in widening to [double] right away (without considering [decimal]) - see below.

  • PowerShell seemingly never auto-selects an integer type smaller than [int]:

    • ('1' - 0).GetType().FullName yields System.Int32 (an [int]), even though integer 1 would fit into [int16] or even [byte].
  • The result of a calculation never uses a smaller type than either of the operands:

    • Both 1 + [long] 1 and [long] 1 + 1 yield a [long] (even though the result could fit into a smaller type).
  • Perhaps unexpectedly, PowerShell auto-selects floating-point type [double] for a calculation result that is larger than either operand's type integer type can fit, even if the result could fit into a larger integer type:

    • ([int]::maxvalue + 1).GetType().FullName yields System.Double (a [double]), even though the result would fit into a [long] integer.
    • If one of the operands is a large-enough integer type, however, the result is of that type: ([int]::maxvalue + [long] 1).GetType().FullName yields System.Int64 (a [long]).
  • Involving at least one floating-point type in a calculation always results in [double], even when mixed with an integer type or using all-[float] operands:

    • 1 / 1.0 and 1.0 / 1 and 1 / [float] 1 and [float] 1 / 1 and [float] 1 / [float] 1 all yield a [double]
  • Number literals in source code that don't use a type suffix:

    • Decimal integer literals are interpreted as the smallest of the following types that can fit the value: [int] > [long] > [decimal] > [double](!):

      • 1 yields an [int] (as stated, [int] is the smallest auto-selected type)
      • 214748364 (1 higher than [int]::maxvalue) yields a [long]
      • 9223372036854775808 (1 higher than [long]::maxvalue) yields a [decimal]
      • 79228162514264337593543950336 (1 higher than [decimal]::maxvalue) yields a [double]
    • Hexadecimal integer literals are interpreted as the smallest of the following types that can fit the value: [int] > [long]; that is, unlike with decimal literals, types larger than [long] aren't supported; Caveat: values that have the high bit set result in negative decimal numbers, because PowerShell auto-selects signed integer types:

      • 0x1 yields an [int]

      • 0x80000000 yields an [int] that is a negative value, because the high bit is set: -2147483648, which is the smallest [int] number, if you consider the sign ([int]::MinValue)

      • 0x100000000 (1 more than can fit into an [int] (or [uint32])) yields a [long]

      • 0x10000000000000000 (1 more than can fit into a [long] (or [uint64])) breaks, because [long] is the largest type supported ("the numeric constant is not valid").

      • To ensure that a hexadecimal literal results in a positive number:

        • Windows PowerShell: Use type suffix L to force interpretation as a [long] first, and then (optionally) cast to an unsigned type; e.g. [uint32] 0x80000000L yields 2147483648, but note that this technique only works up to 0x7fffffffffffffff, i.e., [long]::maxvalue; as suggested above, use a conversion from a string as a workaround (e.g., [uint64] '0xffffffffffffffff').

        • PowerShell [Core] 6.2+: Use type suffix us, u, or ul, as needed; e.g.: 0x8000us -> 32768 ([uint16]), 0x80000000u -> 2147483648 ([uint32]), 0x8000000000000000ul -> 9223372036854775808 ([uint64])

    • Binary integer literals (PowerShell [Core] 7.0+) are interpreted the same way as hexadecimal ones; e.g., 0b10000000000000000000000000000000 == 0x80000000 == -2147483648 ([int])

    • Floating-point or exponential notation literals (which are only recognized in decimal representation) are always interpreted as a [double], no matter how small:

      • 1.0 and 1e0 both yield a [double]