出现情景:
0.1+0.2 // 0.300000000000000040.1+0.7 // 0.79999999999999990.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=>1100.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+308Number.MIN_VALUE; // 5e-324// 超出这两个边界的话Number.MAX_VALUE*10; // Infinity1.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; // -9007199254740991Number.MAX_SAFE_INTEGER; // 9007199254740991Number.MIN_SAFE_INTEGER ; // -9007199254740991
超过这个范围的整数,JS依旧可以运算,但不保证运算结果的精度。
Math.pow(2, 53) ; // 9007199254740992Math.pow(2, 53) + 1; // 90071992547409929007199254740993; //900719925474099290071992547409921; //900719925474099200.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")}
微信公众号:**游戏机室
**
