PowerShell [Math]::Round sometimes not rounding? PowerShell [Math]::Round sometimes not rounding? powershell powershell

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)