Measure-Object で配列の合計を計算した際、計算誤差が発生しまくって大変だったため、再現方法と回避策をメモ。
本記事の概要と結論
Measure-Object で、100 に 0.000001 を 200 回足すと、誤差が発生する。誤差が発生する原因は、 Measure-Object が内部で浮動小数点数を利用しており、浮動小数点の演算時に誤差が発生するためである。(と推測している)
そのため、Measure-Object は、会計処理などの計算誤差が許されない領域に利用してはならない。
以下に、誤差発生の再現方法(=誤差の発生例)と回避策を記載する。
Measure-Object で計算誤差を発生させる
PS C:\> # decimal 型の 配列を作成。最初の要素は 100。
PS C:\> $list = @([decimal]100);
PS C:\> # 配列に 0.000001 を追加し、Measure-Object で合計を計算する。これを 210 回繰り返す。
PS C:\> $tmp = 1..210 | %{ $list += [decimal]0.000001; ($list | measure -sum).Sum }
PS C:\> # 計算結果の 195番目から 205 番目までを出力する。すると 200 番目から誤差が発生している。
PS C:\> $tmp[195..205]
100.000196
100.000197
100.000198
100.000199
100.0002
100.000200999999
100.000201999999
100.000202999999
100.000203999999
100.000204999999
100.000205999999
PS C:\>
Measure-Object の計算誤差を回避する方法
Measure-Object の計算誤差を回避する方法は、Measure-Object を使わずに、decimal 型で合計する(という方法しか思いつきませんでした!)
PS C:\> # Measure-Object の計算誤差を回避する方法
PS C:\> $list = @([decimal]100)
PS C:\> # $result 変数を作成し、配列の数だけ加算する。
PS C:\> $tmp = 1..210 | %{ $list += [decimal]0.000001; $result = 0; ($list | %{ $result += $_ }); $result }
# 計算結果の 200 番目以降も誤差が発生していない。
PS C:\> $tmp[195..205]
100.000196
100.000197
100.000198
100.000199
100.000200
100.000201
100.000202
100.000203
100.000204
100.000205
100.000206
PS C:\>
結論
Measure-Object は、会計処理などの計算誤差が許されない領域に利用してはならない。当たり前の結論です。