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

前端 未结 2 882
抹茶落季
抹茶落季 2021-01-14 02:12

Consider the following Powershell snippet:

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


        
2条回答
  •  甜味超标
    2021-01-14 02:37

    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]

提交回复
热议问题