2017-09-18 5 views
3

Ich habe Kampf versuchen, herauszufinden, warum einige Male die Subtraktion von zwei gerundeten Werten gibt mir einen nicht gerundeten Wert. Ist wie wenn 2.2 - 1.1 = 1.10000001.Powershell [Math] :: Round Runden manchmal nicht?

ich diesen Block von einem Skript in Powershell haben:

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" 
} 

Der beunruhigende Teil ist:

$usedSpaceGB = $sizeGB - $freeSpaceGB 

Die [Math] :: Round() Methode scheint wie beabsichtigt zu arbeiten, denn Ich kann die gerundeten Werte in den Variablen $ sizeGB und $ freeSpaceGB sehen. Hier sind einige Debug-Ausgabe Beispiele:

debug output 2

  1. Ich verstehe nicht, warum in einigen Fällen die Subtraktion zweier zuvor gerundete Werte ergibt sich ein nicht gerundeter Wert, wie Sie in Beispiel Bilder sehen .
  2. dieses Verhalten in der gleichen genauen Positionen der Schleife der Fall ist, können sagen, in der 7, 10 und 18 Iteration.

ich diese Daten in eine HTML-Datei exportieren, um die Tabelle mit dieser Werte nicht gerundet ungerade aussieht:

enter image description here

ich auch das Subtraktionsergebnis dies durch Abrunden „fixiert“ Gerade jetzt, aber ich Ich frage mich, warum das passiert. Ich hoffe, dass mir jemand helfen kann zu verstehen, was vor sich geht.

+7

I '$ disks' nehmen ist eine Sammlung von [' Win32_LogicalDisk'] (https://msdn.microsoft.com/library/aa394173.aspx) Instanzen? Wenn das so ist, sind "FreeSpace" und "Size" Integer-Typen (beide "UInt64"), aber indem sie an [Math.Round] übergeben werden (https://msdn.microsoft.com/library/system.math.round .aspx) erhalten Sie eine 'Double' zurück, die dann dem üblichen Problem von [Fließkommazahlen, die einige Zahlen nicht genau darstellen können] unterliegt (https://stackoverflow.com/questions/1089018/why- cant-decimal-numbers-wird-genau-in-binary dargestellt. Ich würde nur die Endausgabe runden, keine Zwischenwerte. – BACON

Antwort

3

Okay, versuchen wir dies als eine Antwort ...

ich $disks gehe davon ist eine Sammlung von Win32_LogicalDisk Fällen, in welchem ​​Fall die FreeSpace und Size Eigenschaften sind Integer-Typen (insbesondere UInt64). Wenn $disks eine andere Art (en) enthält, sind diese beiden Eigenschaften wahrscheinlich ganze Zahlen von einer Art, da wir mit Bytezählwerte zu tun hat.

Dies ist etwas fremd auf die Frage, aber in dieser Linie ...

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

... $percentFree wird ein Double enthalten, obwohl Sie the Math.Round overload that rounds to the nearest integer sind aufrufen, weil das der Rückgabetyp dieser Methode ist. Sie können dies überprüfen durch Auswertung ...

$percentFree.GetType() 

Wenn Sie wollen/erwarten $percentFree einen Integer-Typ enthalten, dann müssen Sie es auf ein, wie dieser ...

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

Die obige werfen auch gilt, wenn Sie Math.Round verwenden, um das nächste Hundertstel abzurunden ...

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

... denn natürlich, dass -Methodenüberladung einen Gleitkommatyp da durch d zurückzukehren hat Definition, ein Integer-Typ konnte keine Bruchteile eines Wertes speichern.So enthalten $sizeGB und $freeSpaceGB Gleitkommawerte (insbesondere Double) so, wenn diese Linie ...

$usedSpaceGB = $sizeGB - $freeSpaceGB 

... wird $usedSpaceGB wird auch eine Double enthalten ausgeführt, wobei in diesem Fall alle Wetten ab, soweit es being able to exactly represent the resulting value.

Hoffentlich erklärt das, warum das passiert. Soweit, wie Sie Ihren Code zu verbessern, zuerst würde ich empfehlen, keine Rundung auf Zwischenwerte zu tun ...

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

... die deutlicher geschrieben werden kann und prägnant als ...

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

Dies wird Floating-Point-Näherungen nicht entfernen, wie Sie sehen, aber $usedSpaceGB wird näher an den tatsächlichen Wert sein, da Sie nicht basierend auf den bereits abgerundeten (die einige Informationen verwirft) Werte von $sizeGB und $freeSpaceGB berechnen.

In den vorherigen Schnipsel, $usedSpaceGB ist immer noch eine Double, so ist es immer noch möglich, es berechnet einen Wert, der nicht genau dargestellt werden kann. Da dieser Wert für die Anzeige formatiert wird oder sonst auf eine string (als HTML) serialisiert werden würde ich nur vergessen, über Math.Round und lassen string formatting die Rundung für Sie so behandeln ...

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

... oder diese ...

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

Nun Änderung Math.Round für .ToString ('N2') löste das Problem. Vielen Dank. – smartdan