Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 287
Mark Allison Avatar asked Dec 11 '16 16:12

Mark Allison


2 Answers

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]
like image 81
mklement0 Avatar answered Nov 15 '22 05:11

mklement0


Your problem is that the ToUint64 doesn't understand the Powershell syntax. You could get around it by doing:

($MemoryFromString / 1GB) * 1GB

As the $MemoryFromString will be converted its numeric value before the division.

This works because at the point of division Powershell attempts to convert the string to a number using its rules, rather than the .Net rules that are baked into ToUInt64. As part of the conversion if spots the GB suffix and applies it rules to expand the "1GB" string to 1073741824

EDIT: Or as PetSerAl pointed out, you can just do:

($MemoryFromString / 1)
like image 37
Sean Avatar answered Nov 15 '22 06:11

Sean