问题
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' - 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, and0b
for binary integers (PowerShell [Core] 7.0+)number-type suffixes:
L
for[long]
([System.Int64]
), andD
for[decimal]
([System.Decimal]
); e.g.,'1L' - 0
yields a[long]
.
Note that C# usesM
instead ofD
and instead usesD
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), 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' - 1
yields999
.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
yields1023
; 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
orUL
(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, because0xffffffff
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 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().Name
yieldsInt64
, because the value is[int32]::MaxValue + 1
, and was therefore implicitly widened to[int64]
.(9223372036854775808).GetType().Name
yieldsDecimal
, because the value is[int64]::MaxValue + 1
, and was therefore implicitly widened to[decimal]
.(79228162514264337593543950336).GetType().Name
yieldsDouble
, 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
yieldsInt64
, 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
yieldsSystem.Int32
(an[int]
), even though integer1
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).
- 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().FullName
yieldsSystem.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
yieldsSystem.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
and1.0 / 1
and1 / [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
yields2147483648
, 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.0
and1e0
both yield a[double]
来源:https://stackoverflow.com/questions/41088561/how-to-convert-to-uint64-from-a-string-in-powershell-string-to-number-conversio