PowerShell [数学] ::圆形有时不圆?

时间:2017-09-18 19:18:43

标签: powershell rounding

我一直在努力弄清楚为什么有时候两个舍入值的减法给我一个非舍入值。就像2.2 - 1.1 = 1.10000001。

我在PowerShell中有这个脚本块:

foreach($disk in $disks) 
{
    $size = $disk.Size
    $freespace = $disk.FreeSpace
    $percentFree = [Math]::Round(($freespace / $size) * 100)
    $sizeGB = [Math]::Round($size / 1073741824, 2)
    $freeSpaceGB = [Math]::Round($freespace / 1073741824, 2)
    $usedSpaceGB = $sizeGB - $freeSpaceGB

    #view the variable values in every iteration
    Write-Debug "`$size = $size"
    Write-Debug "`$freespace = $freespace"
    Write-Debug "`$percentFree = $percentFree%"
    Write-Debug "`$sizeGB = $sizeGB"
    Write-Debug "`$freeSpaceGB = $freeSpaceGB"
    Write-Debug "`$usedSpaceGB = $usedSpaceGB"
}

令人不安的部分是:

$usedSpaceGB = $sizeGB - $freeSpaceGB

[Math] :: Round()方法似乎按预期工作,因为我可以看到存储在$ sizeGB和$ freeSpaceGB变量中的舍入值。以下是一些调试输出示例:

debug output 1  debug output 2

  1. 我不明白为什么在某些情况下减去两个先前舍入的值会导致非舍入值,如示例图像中所示。
  2. 此行为发生在循环的相同位置,例如在7,10和18次迭代中。
  3. 我将此数据导出到HTML文件,因此表格看起来很奇怪,这些值没有舍入:

    enter image description here

    现在我"修复"这也是Rounding的减法结果,但我想知道为什么会发生这种情况。我希望有人能帮我理解发生了什么。

1 个答案:

答案 0 :(得分:4)

好吧,让我们试试这个......

我假设$disksWin32_LogicalDisk个实例的集合,在这种情况下,FreeSpaceSize属性是整数类型(具体来说,UInt64) 。如果$disks包含其他一些类型,那么这两个属性可能是某种整数,因为我们正在处理字节数。

这与这个问题有点无关,但在这一行......

$percentFree = [Math]::Round(($freespace / $size) * 100)

... $percentFree即使您正在调用the Math.Round overload that rounds to the nearest integer,也会包含Double,因为这是该方法的返回类型。您可以通过评估......

来检查这一点
$percentFree.GetType()

如果您希望/期望$percentFree包含整数类型,那么您需要将其强制转换为一个,如下所示...

$percentFree = [UInt64] [Math]::Round(($freespace / $size) * 100)

当您使用Math.Round舍入到最接近的百分之一时,上述情况也适用

$sizeGB = [Math]::Round($size / 1073741824, 2)
$freeSpaceGB = [Math]::Round($freespace / 1073741824, 2)

...因为,当然,该方法重载必须返回一个浮点类型,因为根据定义,整数类型不能存储值的分数。因此,$sizeGB$freeSpaceGB包含浮点值(特别是Double)所以当这一行......

$usedSpaceGB = $sizeGB - $freeSpaceGB

...已执行$usedSpaceGB还将包含Double,在这种情况下,所有投注均已关闭being able to exactly represent the resulting value

希望这能解释为什么会这样。至于如何改进代码,首先我建议不要对中间值进行任何舍入...

$sizeGB = $size / 1073741824
$freeSpaceGB = $freespace / 1073741824
$usedSpaceGB = [Math]::Round($sizeGB - $freeSpaceGB, 2)

......可以更清晰,更简洁地写成......

$sizeGB = $size / 1GB
$freeSpaceGB = $freespace / 1GB
$usedSpaceGB = [Math]::Round($sizeGB - $freeSpaceGB, 2)

这不会消除您所看到的浮点近似值,但$usedSpaceGB将更接近实际值,因为您不是基于已经舍入的(放弃某些信息)值进行计算$sizeGB$freeSpaceGB

在之前的代码段中,$usedSpaceGB仍然是Double,所以它仍然可能会计算出一些无法准确表示的值。由于此值正在格式化以供显示,或者必须序列化为string(作为HTML),我会忘记Math.Round并让string formatting像这样处理舍入。

$usedSpaceGBText = (($size - $freeSpace) / 1GB).ToString('N2')

......或者......

$usedSpaceGBText = '{0:N2}' -f (($size - $freeSpace) / 1GB)