出现情景:
0.1+0.2 // 0.30000000000000004
0.1+0.7 // 0.7999999999999999
0.1+0.3 // 0.4
Number类型相关:
Js中的数字类型只有Number一种,采用IEEE-754标准中的“双精度浮点数”表示一个数字,并没有再去区分整数类型和浮点数类型。
IEEE-754标准表示法:
一种二进制表示法,能够精确地表示分数,比如1/2,1/8,1/1024。遗憾的是,我们常用的分数都是十进制分数1/10,1/100等,二进制浮点数表示法并不能精确地表示0.1这样的简单十进制数字,因为用二进制表示是无限循环,如下:
十进制整数转二进制
1=>1 2=>10 3=>101 4=>100 5=>101 6=>110
0.1 => 0.0001 1001 1001 1001…(无限循环)
0.2 => 0.0011 0011 0011 0011…(无限循环)
存储结构:
在IEEE-754中,双精度浮点数(double precision)占用64bit来存储,如下:
数值范围:
从存储结构中可以看出, 指数部分的长度是11个二进制,即指数部分能表示的最大值是 2047(2^11-1);
取中间值进行偏移,用来表示负指数,也就是说指数的范围是 [-1023,1024]
因此,这种存储结构能够表示的数值范围为 2^1024 到 2^-1023 ,超出这个范围的数无法表示 。2^1024 和 2^-1023 转换为科学计数法如下所示:
最大值 1.7976931348623157 × 10^308
最小值 5 × 10^-324
这两个边界值通过固定变量来获取:
Number.MAX_VALUE; // 1.7976931348623157e+308
Number.MIN_VALUE; // 5e-324
// 超出这两个边界的话
Number.MAX_VALUE*10; // Infinity
1.7976931348623157E+309; // Infinity
-1.7976931348623157E+309; // -Infinity
数值精度:
在 64 位的二进制中,符号位决定了一个数的正负,指数部分决定了数值的大小,小数部分决定了数值的精度。
JavaScript 能表示并进行精确算术运算的整数范围为:[-2^53-1,2^53-1],即从最小值 -9007199254740991 到最大值 9007199254740991 之间的范围 。
Math.pow(2, 53)-1; // 9007199254740991
-Math.pow(2, 53)-1; // -9007199254740991
Number.MAX_SAFE_INTEGER; // 9007199254740991
Number.MIN_SAFE_INTEGER ; // -9007199254740991
超过这个范围的整数,JS依旧可以运算,但不保证运算结果的精度。
Math.pow(2, 53) ; // 9007199254740992
Math.pow(2, 53) + 1; // 9007199254740992
9007199254740993; //9007199254740992
90071992547409921; //90071992547409920
0.923456789012345678;//0.9234567890123456
数字精度丢失的经典问题处理:
浮点数相加
0.1 + 0.2 != 0.3 // true
toFixed的处理不会四舍五入【有些Chrome可以自动四舍五入,但是也有不会四舍五入存在】
(2.345).toFixed(2) // "2.34"
// mac下的版本 75.0.3770.100(正式版本) (64 位)正常四舍五入
解决方法:
浮点数的四则运算处理:
/**
* 判断obj是否为一个整数
*/
function isInt(param) {
return Math.floor(param) === param;
}
/**
* 将一个浮点数转成整数,如:3.14 => 314,倍数是100
* @param floatNum {number} 小数
* @return {times: 100, num: 314} {object} 对象
*/
function toInt(floatNum) {
let res = {
times: 1,
num: 0
}
// 是否负数
let isNegative = floatNum < 0;
// 整数就直接返回
if (isInt(floatNum)) {
res.num = floatNum;
return res;
}
let len = String(floatNum).split(".")[1].length;
let times = Math.pow(10, len);
// 为了避免乘以times之后,得到一个无限小数比原值小或者大,所以加上0.5利用parseInt取整
let intNum = parseInt(Math.abs(floatNum) * times + 0.5, 10);
if (isNegative) {
intNum = -intNum;
}
res.times = times;
res.num = intNum;
return res;
}
/**
* 实现加减乘除,去报不丢失精度
* @param a {number} 数字a
* @param b {number} 数字b
* @param op {string} 运算类型 加减乘除 add/subtract/multiply/divide
*/
function opration(a, b, op) {
let aObj = toInt(a);
let bObj = toInt(b);
// 整数
let aNum = aObj.num;
let bNum = bObj.num;
// 倍数
let aTimes = aObj.times;
let bTimes = bObj.times;
// 最大倍数,初始化
let maxTimes = aTimes;
// 同步两个参数大小
if (aTimes > bTimes) {
bNum = bNum * (aTimes / bTimes);
maxTimes = aTimes;
} else if (aTimes < bTimes) {
aNum = aNum * (bTimes / aTimes);
maxTimes = bTimes;
}
// 四则运算
let result = 0;
switch (op) {
case "add":
result = (aNum + bNum) / maxTimes;
break;
case "subtract":
result = (aNum - bNum) / maxTimes;
break;
case "multiply":
result = (aNum * bNum) / Math.pow(maxTimes, 2);
break;
case "divide":
result = aNum / bNum;
break;
}
return result;
}
// 加法
export let add = (a, b) => {
return opration(a, b, "add");
}
// 减法
export let subtract = (a, b) => {
return opration(a, b, "subtract")
}
// 乘法
export let multiply = (a, b) => {
return opration(a, b, "multiply");
}
// 除法
export let divide = (a, b) => {
return opration(a, b, "divide")
}
微信公众号:**游戏机室
**