PowerShell [Math]::Round sometimes not rounding?
Alright, let's try this as an answer...
I am assuming $disks
is a collection of Win32_LogicalDisk
instances, in which case the FreeSpace
and Size
properties are integer types (specifically, UInt64
). If $disks
contains some other type(s), those two properties are likely integers of some kind since we're dealing with byte counts.
This is somewhat extraneous to the question, but in this line...
$percentFree = [Math]::Round(($freespace / $size) * 100)
...$percentFree
will contain a Double
even though you're calling the Math.Round
overload that rounds to the nearest integer because that's the return type of that method. You can inspect this by evaluating...
$percentFree.GetType()
If you want/expect $percentFree
to contain an integer type then you need to cast it to one, like this...
$percentFree = [UInt64] [Math]::Round(($freespace / $size) * 100)
The above also applies when you are using Math.Round
to round to the nearest hundredth...
$sizeGB = [Math]::Round($size / 1073741824, 2)$freeSpaceGB = [Math]::Round($freespace / 1073741824, 2)
...because, of course, that method overload has to return a floating-point type since, by definition, an integer type could not store fractions of a value. Thus, $sizeGB
and $freeSpaceGB
contain floating-point values (specifically Double
) so when this line...
$usedSpaceGB = $sizeGB - $freeSpaceGB
...is executed $usedSpaceGB
will also contain a Double
, in which case all bets are off as far as it being able to exactly represent the resulting value.
Hopefully that explains why this is happening. As far as how to improve your code, first I would recommend not doing any rounding on intermediate values...
$sizeGB = $size / 1073741824$freeSpaceGB = $freespace / 1073741824$usedSpaceGB = [Math]::Round($sizeGB - $freeSpaceGB, 2)
...which can be written more clearly and concisely as...
$sizeGB = $size / 1GB$freeSpaceGB = $freespace / 1GB$usedSpaceGB = [Math]::Round($sizeGB - $freeSpaceGB, 2)
This won't eliminate floating-point approximations like you're seeing, but $usedSpaceGB
will be closer to the actual value since you're not computing based on the already-rounded (which discards some information) values of $sizeGB
and $freeSpaceGB
.
In the previous snippets, $usedSpaceGB
is still a Double
so it's still possible it computes to some value that can't be represented exactly. Since this value is being formatted for display or otherwise must be serialized to a string
(as HTML) I would just forget about Math.Round
and let string
formatting handle the rounding for you like this...
$usedSpaceGBText = (($size - $freeSpace) / 1GB).ToString('N2')
...or this...
$usedSpaceGBText = '{0:N2}' -f (($size - $freeSpace) / 1GB)