问题
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?
回答1:
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)
回答2:
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' - 0would 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):
0xfor hexadecimal integers, and0bfor binary integers (PowerShell [Core] 7.0+)number-type suffixes:
Lfor[long]([System.Int64]), andDfor[decimal]([System.Decimal]); e.g.,'1L' - 0yields a[long].
Note that C# usesMinstead ofDand instead usesDto 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), andUL([uint64]).
- PowerShell [Core] 6.2+ now supports additional suffixes:
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' - 1yields999.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' - 1yields1023; 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,UorUL(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] 0xfffffffffails, because0xffffffffis 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
Lto force interpretation as an[int64]first, which results in expected positive value4294967295, 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'
- Append
- Note: In PowerShell [Core] 6.2+, you can use type suffix
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().NameyieldsInt64, because the value is[int32]::MaxValue + 1, and was therefore implicitly widened to[int64].(9223372036854775808).GetType().NameyieldsDecimal, because the value is[int64]::MaxValue + 1, and was therefore implicitly widened to[decimal].(79228162514264337593543950336).GetType().NameyieldsDouble, 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().nameyieldsInt64, 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().FullNameyieldsSystem.Int32(an[int]), even though integer1would fit into[int16]or even[byte].
The result of a calculation never uses a smaller type than either of the operands:
- Both
1 + [long] 1and[long] 1 + 1yield a[long](even though the result could fit into a smaller type).
- Both
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().FullNameyieldsSystem.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().FullNameyieldsSystem.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.0and1.0 / 1and1 / [float] 1and[float] 1 / 1and[float] 1 / [float] 1all 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](!):1yields 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:0x1yields an[int]0x80000000yields 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
Lto force interpretation as a[long]first, and then (optionally) cast to an unsigned type; e.g.[uint32] 0x80000000Lyields2147483648, but note that this technique only works up to0x7fffffffffffffff, 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, orul, 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.0and1e0both yield a[double]
来源:https://stackoverflow.com/questions/41088561/how-to-convert-to-uint64-from-a-string-in-powershell-string-to-number-conversio