最近開發的項目新需求中,須要一個金額的輸入框,其輸入上限爲16位整數帶兩位小數的浮點類型,因爲前臺顯示的金額是字符串型,在傳遞給後臺時使用了parseFloat()
方法,卻發如今測試校驗最大臨界值時,發生了四捨五入,最終致使校驗失敗,由此引起了我對JS浮點數表示和運算了深刻理解。bash
因爲計算機的二進制實現和位數限制,有些數沒法有限表示。就像一些無理數不能有限表示,如 圓周率 3.1415926...,1.3333... 等。JS 遵循 IEEE 754 規範,採用雙精度存儲(double precision),佔用 64 bit。測試
由於在計算機最底層,數值的運算和操做都是採用二進制實現的,因此計算機沒有辦法精確表示浮點數,而只能用二進制近似相等的去表示浮點數的小數部分。ui
0.1 >> 0.0001 1001 1001 1001…(1001無限循環)
0.2 >> 0.0011 0011 0011 0011…(0011無限循環)
複製代碼
當進行計算或其餘操做時時,四捨五入(逢1進,逢0舍)將會致使最終的運算結果存在誤差。spa
而大整數也存在一樣的問題,由於表示尾數的尾數只有52位,所以 JS 中能精準表示的最大整數是 Math.pow(2, 53),即十進制9007199254740992。3d
9007199254740992 >> 10000000000000...000 // 共計 53 個 0
9007199254740992 + 1 >> 10000000000000...001 // 中間 52 個 0
9007199254740992 + 2 >> 10000000000000...010 // 中間 51 個 0
9007199254740992 + 1 // 丟失 //9007199254740992
9007199254740992 + 2 // 未丟失 //9007199254740994
9007199254740992 + 3 // 丟失 //9007199254740992
9007199254740992 + 4 // 未丟失 //9007199254740996
複製代碼
由此可知,十進制中的有窮數值,在計算機底層,多是0、1循環的無限數值。code
在Java、C、C++中,均有對浮點數值的特殊處理,如Java的BigDecimal類型就是用來解決這一浮點數問題。cdn
0.1 + 0.2 != 0.3 // true
複製代碼
普通的整數計算比較不太容易出錯,除非計算範圍超出 Math.pow(2, 53)blog
9999999999999999 == 10000000000000001 // true
複製代碼
這種狀況在一些金額的計算中較容易出現,但也是最容易被忽視的一種,當用戶在輸入框中輸入一個位數較多的字符串(不單單包含大數值,小數點後位數過長也包含在這一案例中),並在前臺使用JS將其轉換爲數值,獲得的結果每每是四捨五入帶有誤差的值ci
parseFloat(0.9); //0.9
parseFloat(9999999999999999.9) //10000000000000000
parseInt("9999999999999999"); //10000000000000000
parseFloat(9.999999999999999); //10
複製代碼
var num = 1.335;
num.toFixed(2); //1.33
複製代碼
一般解決這一問題,採用的都是將浮點部分轉換成整數後進行計算開發
//浮點數轉換爲整數
function toInt(num){
var rel = {};
var str,pos,len,times;
str = (num < 0) ? -num + '' : num + '';
pos = str.indexOf('.');
len = str.substr(pos+1).length;
times = Math.pow(10, len);
rel.times = times;
rel.num = num;
return rel;
}
//計算過程
function operate(a,b,op){
var d1 = toInt(a);
var d2 = toInt(b);
var max = d1.times > d2.times ? d1.times : d2.times;
var rel;
switch(op){
case "+" :
rel = (d1.num * max + d2.num * max) / max;
break;
case "-" :
rel = (d1.num * max - d2.num * max) / max;
break;
case "*" :
rel = ((d1.num * max) * (d2.num * max)) / (max * max);
break;
case "/" :
rel = (d1.num * max) / (d2.num * max);
break;
}
return rel;
}
var rel = operate(0.3,0.1,"+"); //0.4
複製代碼
前臺不對這類字符串進行數值轉換,傳到後臺後,由後臺進行處理
function toFixed(num, s) {
var times = Math.pow(10, s)
var des = num * times + 0.5
des = parseInt(des, 10) / times
return des + ''
}
複製代碼