mclayton helpfully linked to this answer to a related C# question in a comment, and the solution there can indeed be adapted to PowerShell, if working with or conversion to type [decimal]
is acceptable:
# Define $a as a [decimal] literal (suffix 'd')
# This internally records the scale (number of decimal places) as specified.
$a = 0.0001d
# [decimal]::GetBits() allows extraction of the scale from the
# the internal representation:
[decimal]::GetBits($a)[-1] -shr 16 -band 0xFF # -> 4, the number of decimal places
The System.Decimal.GetBits
method returns an array of internal bit fields whose last element contains the scale in bits 16 - 23 (8 bits, even though the max. scale allowed is 28
), which is what the above extracts.
Note: A PowerShell number literal that is a fractional number without the d
suffix - e.g., 0.0001
becomes a [double]
instance, i.e. a double-precision binary floating-point number.
PowerShell automatically converts [double]
to [decimal]
values on demand, but do note that there can be rounding errors due to the differing internal representations, and that [double]
can store larger numbers than [decimal]
can (although not accurately).
A [decimal]
literal - one with suffix d
(note that C# uses suffix m
) - is parsed with a scale exactly as specified, so that applying the above to 0.000d
and 0.010d
yields 3
in both cases; that is, the trailing zeros are meaningful.
This does not apply if you (implicitly) convert from [double]
instances such as 0.000
and 0.010
, for which the above yields 0
and 2
, respectively.
A string-based solution:
To offer a more concise (also culture-invariant) alternative to Bender The Greatest's helpful answer:
$a = 0.0001
("$a" -replace '.+\.').Length # -> 4, the number of decimal places
Caveat: This solution relies on the default string representation of a [double]
number, which need not match the original input format; for instance, .0100
, when stringified later, becomes '0.01'
; however, as discussed above, you can preserve trailing zeros if you start with a [decimal]
literal: .0100d
stringifies to '0.0100'
(input number of decimals preserved).
"$a"
, uses an expandable string (PowerShell's string interpolation) to create a culture-invariant string representation of the number so as to ensure that the string representation uses .
as the decimal mark.
In effect, PowerShell calls $a.ToString([cultureinfo]::InvariantCulture)
behind the scenes.[1].
By contrast, .ToString()
(argument-less) applies the rules of the current culture, and in some cultures it is ,
- not .
- that is used as the decimal mark.
Caveat: If you use just $a
as the LHS of -replace
, $a
is implicitly stringified, in which case you - curiously - get culture-sensitive behavior, as with .ToString()
- see this GitHub issue.
-replace '.+\.'
effectively removes all characters up to and including the decimal point from the input string, and .Length
counts the characters in the resulting string - the number of decimal places.
[1] Note that casts from strings in PowerShell too use the invariant culture (effectively, ::Parse($value, [cultureinfo]::InvariantCulture)
is called) so that in order to parse a a culture-local string representation you'll need to use the ::Parse()
method explicitly; e.g., [double]::Parse('1,2')
, not [double] '1,2'
.