上次介紹到利用 toPrecision 避免浮點數的計算偏差
那可以一直使用固定的 Precision 嗎?
不行,看下面的計算範例

trace((10000 – 10000.0001).toPrecision(8)) // -0.000099999999

浮點數有效位數是 15-16 位
10000 運算元就已經用掉 5 位整數有效數字
計算結果沒有整數部分,小數有效數字只剩下 16 – 5 = 11 位而已

再看 toPrecision 雖然只有用到 8 可是由於計算結果小數下 4 位都是 0
8 + 4 = 12 實際上是取到小數下 12 位
超出剛剛推算的 11 位有效數字
所以才會導致無法正確去除偏差

有一個比較偷懶的方式,就是運算元與計算結果通通都只取 8 位有效數字
這樣就不會遇到計算偏差,缺點是不能使用超過 8 位的有效數值

假如想要用超過 8 位有效數值,又要避開計算偏差怎麼辦?
那得自行要從運算元與計算結果,計算出結果數值最大有效位數了
以下面是藉由計算結果最大有效位數,來修正浮點數計算偏差的 function

/**
 * 藉由計算結果最大有效位數,修正浮點數計算偏差
 * @param resNo 計算結果數值
 * @param operands 所有參與計算的數值
 * @return 修正後的計算結果數值,回傳 0 表示結果無有效位數可用
 * @author Ticore Shih
 */
function fixFloatPrecision(resNo:Number, ...operands:Array):Number{
 operands = operands.concat(resNo);
 var absOperands:Array = [];
 for each(var i:Number in operands) {
  absOperands.push(Math.abs(i));
 }
 var maxIntPlace:Number = Math.log(Math.max.apply(null, absOperands)) / Math.LN10;
 var maxDecPlace:Number = 16 - maxIntPlace;
 var resIntPlace:Number = Math.log(Math.abs(resNo)) / Math.LN10;
 var maxResPrec:uint = Math.max(maxDecPlace + resIntPlace, 0);
 return maxResPrec ? parseFloat(resNo.toPrecision(maxResPrec)) : 0;
}

以下是減法運算修正偏差的測試程式:

function testSub(no1:Number, no2:Number):void{
 var res:Number = no1 - no2;
 trace(no1 + " - " + no2 + " =\n\t\t" + res);
 trace("\t\t" + fixFloatPrecision(res, no1, no2), "(precision fixed)");
 trace("");
 return;
}


testSub(123456, 123456.00001);
testSub(1234567, 1234000.0001);
testSub(1234567, 1100000.0001);
testSub(10000000, 10000000.01);
testSub(10, 10.00001);
testSub(10, 10.000001);
testSub(0.0000023, 0.00000230002);
testSub(100000000000000, 100000000000000.02);
testSub(10000000000000, 10000000000000.02);

輸出結果包含偏差修正前與修正後的數值:

123456 - 123456.00001 =
  -0.000010000003385357559
  -0.00001 (precision fixed)

1234567 - 1234000.0001 =
  566.999899999937
  566.9999 (precision fixed)

1234567 - 1100000.0001 =
  134566.99989999994
  134566.9999 (precision fixed)

10000000 - 10000000.01 =
  -0.009999999776482582
  -0.01 (precision fixed)

10 - 10.00001 =
  -0.000009999999999621423
  -0.00001 (precision fixed)

10 - 10.000001 =
  -9.999999992515995e-7
  -0.000001 (precision fixed)

0.0000023 - 0.00000230002 =
  -2.0000000000128558e-11
  -2e-11 (precision fixed)

100000000000000 - 100000000000000.02 =
  -0.015625
  0 (precision fixed)

10000000000000 - 10000000000000.02 =
  -0.01953125
  -0.02 (precision fixed)

目前看起來可以修正絕大部分的浮點計算偏差
不需要自己設定愚蠢的固定小數位數
且能夠最大有效利用浮點數有效位數