为什么 0.1 + 0.2 !== 0.3, 而 0.05 + 0.25 === 0.3
js在计算浮点数时可能不够准确,会产生舍入误差的问题,这是使用基于IEEE754二进制数值的浮点计算的通病,并非ECMAScript一家,其他使用相同数值格式的语言也存在这个问题。
和其它语言如 Java 和 Python 不同,JavaScript 中所有数字包括整数和小数都只有一种类型:Number
。
使用 64 位固定长度来表示,也就是标准的double 双精度浮点数
。
这样的存储结构优点是可以归一化处理整数和小数,节省存储空间
。
这64
个比特又可分为三个部分,即:
第1位
: 是符号的标志位(S), 0代表正数,1代表负数
第1-11位
: 指数位(E), 存储指数(exponent),用来表示次方数
第12-63位
: 尾数(M), 这52 位是尾数,超出的部分自动进一舍零
将小数转化为整数, 进行计算后, 再转化为小数就好了。
当然已经有成熟的工具库可以使用了, 例如Math.js, BigDecimal.js, number-precision
等等
整数的乘法运算是准确的,这里我们将浮点数的乘法运算转化为整数乘法,然后除以10的他们小数位数之和次方
function FloatMul(arg1,arg2){
var m=0,s1=arg1.toString(),s2=arg2.toString();
try{
m+=s1.split(".")[1].length
}catch(e){}
try{
m+=s2.split(".")[1].length
}catch(e){}
return Number(s1.replace(".",""))*Number(s2.replace(".",""))/Math.pow(10,m)
}
这里我们将浮点数分别乘以小数位最大的之后,再相加,然后除以10的小数位数最大的次方
function FloatAdd(arg1,arg2){
var r1,r2,m;
try{
r1=arg1.toString().split(".")[1].length
}catch(e){
r1=0
}
try{
r2=arg2.toString().split(".")[1].length
}catch(e){
r2=0
}
m=Math.pow(10,Math.max(r1,r2))
return (arg1*m+arg2*m)/m
}
function FloatSub(arg1,arg2){
var r1,r2,m,n;
try{
r1=arg1.toString().split(".")[1].length
}catch(e){
r1=0
}
try{
r2=arg2.toString().split(".")[1].length
}catch(e){
r2=0
}
m=Math.pow(10,Math.max(r1,r2));
//动态控制精度长度
n=(r1>=r2)?r1:r2;
return ((arg1*m-arg2*m)/m).toFixed(n);
}
类似加法,将两个数各自乘以10的他们小数位最大的次方,再相除,然后乘以1e(除数小数位 - 被除数小数位)
function FloatDiv(arg1,arg2){
var t1=0,t2=0,r1,r2;
try{
t1=arg1.toString().split(".")[1].length
}catch(e){}
try{
t2=arg2.toString().split(".")[1].length
}catch(e){}
with(Math){
r1=Number(arg1.toString().replace(".",""))
r2=Number(arg2.toString().replace(".",""))
return (r1/r2)*pow(10,t2-t1);
}
}
整数运算在数字特别大的时候,计算结果的尾数也会不准确。如超过9个9乘以本身的乘法运算时,计算出的结果最大是999999998000000000,正常结果个位数应该为1,但是js运算结果将个位数去掉了,类似数据比较大时,经验证,7位数乘以7位数还是正常的,超过后就不准确了。
JS 提供Number.MAX_SAFE_INTEGER
常量来表示 最大安全整数,Number.MIN_SAFE_INTEGER
常量表示最小安全整数:
const minInt = Number.MIN_SAFE_INTEGER;
console.log(minInt); // → -9007199254740991
console.log(minInt - 5); // → -9007199254740996
// notice how this outputs the same value as above
console.log(minInt - 4); // → -9007199254740996
BigInt是ECMAScript 2020的新功能之一,它可以处理超过JavaScript Number类型最大值的大数字,最大的范围是2^53 - 1,超过这个范围的数字就会被自动转换为科学计数法。使用BigInt即可轻松处理大数字。
BigInt可以单独作为一种类型使用,也可以通过在数字后面添加n的方式进行创建。例如:
const bigNumber = 123456789123456789n;
// 定义两个超大数字
const num1 = BigInt(9007199254740993); // 最大安全整数
const num2 = BigInt("12345678901234567890"); // 更大的超大数字
// 加法运算
console.log(num1 + num2); // 输出结果为 "12345678901234567893"
// 乘法运算
console.log(num1 * num2); // 输出结果为 "11234567890123456789000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
不能使用严格相等运算符将BigInt
与常规数字进行比较,因为它们的类型不同
console.log(10n === 10); // → false
console.log(typeof 10n); // → bigint
console.log(typeof 10); // → number
可以使用等号运算符,它在处理操作数之前执行隐式类型转换
console.log(10n == 10); // → true
不允许在bigint和 Number 之间进行混合操作,因为隐式类型转换可能丢失信息
10n + 1; // Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions
Math.max(2n, 4n, 6n); // TypeError...
当使用 BigInt 时,带小数的运算会被取整。
// 2n
const expected = 4n / 2n;
// 2n, not 2.5n
const rounded = 5n / 2n;
BigInt转为String时,其标志性的n
会被省略
String(10n); // '10'
'' + 11n; // '11'
bignumber.js
decimal.js