JS浮点数运算多出很多位小数点Bug的解决办法
发布时间:2016-09-10, 15:25:21 分类:HTML | 编辑 off 网址 | 辅助
图集1/1
正文 2958字数 1,779,544阅读
37.5*5.5=206.08 (JS算出来是这样的一个结果,我四舍五入取两位小数)我先怀疑是四舍五入的问题,就直接用JS算了一个结果为:206.08499999999998
怎么会这样,两个只有一位小数的数字相乘,怎么可能多出这么小数点出来。
我Google了一下,发现原来这是JavaScript浮点运算的一个bug。
比如:7*0.8 JavaScript算出来就是:5.6000000000000005
网上找到了一些解决办法,就是重新写了一些浮点运算的函数或直接扩大倍数运算。
下面就把这些方法摘录下来,以供遇到同样问题的朋友参考:
------------------------------------------------------------------------------------------------------
程序代码
(方法一:重写浮点运算的函数)
//除法函数,用来得到精确的除法结果
//说明:javascript的除法结果会有误差,在两个浮点数相除的时候会比较明显。这个函数返回较为精确的除法结果。
//调用:accDiv(arg1,arg2)
//返回值:arg1除以arg2的精确结果
function accDiv(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);
}
}
//给Number类型增加一个div方法,调用起来更加方便。
Number.prototype.div = function (arg){
return accDiv(this, arg);
}
//乘法函数,用来得到精确的乘法结果
//说明:javascript的乘法结果会有误差,在两个浮点数相乘的时候会比较明显。这个函数返回较为精确的乘法结果。
//调用:accMul(arg1,arg2)
//返回值:arg1乘以arg2的精确结果
function accMul(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)
}
//给Number类型增加一个mul方法,调用起来更加方便。
Number.prototype.mul = function (arg){
return accMul(arg, this);
}
//加法函数,用来得到精确的加法结果
//说明:javascript的加法结果会有误差,在两个浮点数相加的时候会比较明显。这个函数返回较为精确的加法结果。
//调用:accAdd(arg1,arg2)
//返回值:arg1加上arg2的精确结果
function accAdd(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
}
//给Number类型增加一个add方法,调用起来更加方便。
Number.prototype.add = function (arg){
return accAdd(arg,this);
}
//减法函数,用来得到精确的减法结果
//说明:javascript的减法结果会有误差,在两个浮点数相加的时候会比较明显。这个函数返回较为精确的减法结果。
//调用:accSubtr(arg1,arg2)
//返回值:arg1减去arg2的精确结果
function accSubtr(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);
}
//给Number类型增加一个subtr 方法,调用起来更加方便。
Number.prototype.subtr = function (arg){
return accSubtr(arg,this);
}
Run code
Cut to clipboard
在你要用的地方包含这些函数,然后调用它来计算就可以了。
比如你要计算:7*0.8 ,则改成 (7).mul(8)
其它运算类似,就可以得到比较精确的结果。
------------------------------------------------------------------------------------------------------
(方法二:将浮点数放大倍数到整型)
//如果在知道小数位个数的前提下,可以考虑通过将浮点数放大倍数到整型(最后再除以相应倍数),再进行运算操作,这样就能得到正确的结果了
<script>
alert(11*(22.9*10)/10);
</script>
Run code
Cut to clipboard
(支付宝)给作者钱财以资鼓励 (微信)→
有过 8 条评论 »
js解释环境里面,0.1+0.2!=0.3 ;结果是true
为了避免产生精度差异,我们要把需要计算的数字乘以 10 的 n 次幂,换算成计算机能够精确识别的整数,然后再除以 10 的 n 次幂;
<script> Math.formatFloat = function(f, digit) { var m = Math.pow(10, digit); return parseInt(f * m, 10) / m; } var numA = 0.1; var numB = 0.2; console.log(0.1+0.2); console.log(0.1+0.2==0.3); console.log(0.1+0.2!=0.3); console.log(Math.formatFloat(numA + numB, 1) === 0.3);</script>
大部分编程语言都是这样处理精度差异的,借用过来处理一下 JS 中的浮点数精度误差。
<script> //浮点转为整数也可能有误差,那么这个问题有没有解决方案? alert(0.57*100); </script>
<script> /** * [scaleNum 通过操作其字符串将一个浮点数放大或缩小] * @param {number} num 要放缩的浮点数 * @param {number} pos 小数点移动位数 * pos大于0为放大,小于0为缩小;不传则默认将其变成整数 * @return {number} 放缩后的数 */ "use strict"; function scaleNum(num, pos) { if (num === 0 || pos === 0) { console.log(1); return num; } let parts = num.toString().split('.'); const intLen = parts[0].length; const decimalLen = parts[1] ? parts[1].length : 0; // 默认将其变成整数,放大倍数为原来小数位数 if (pos === undefined) { console.log(2); return parseFloat(parts[0] + parts[1]); } else if (pos > 0) { // 放大 let zeros = pos - decimalLen; while (zeros > 0) { zeros -= 1; parts.push(0); } } else { // 缩小 let zeros = Math.abs(pos) - intLen; while (zeros > 0) { zeros -= 1; parts.unshift(0); } } const idx = intLen + pos; parts = parts.join('').split(''); parts.splice(idx > 0 ? idx : 0, 0, '.'); console.log(3); return parseFloat(parts.join('')); } alert(scaleNum(0.57)); </script>
针对单个脚本
<script> "use strict"; console.log("这是严格模式。"); </script>
针对单个函数
function strict(){ "use strict"; return "这是严格模式。"; } function notStrict() { return "这是正常模式。"; }
JavaScript中变量提升Hoisting
ECMAScript 6入门 - let和const命令
'use strict'; let
$(".zx>li").click(function() { clearTimeout(cce); }); var cce; $(window).scroll(function() { var wintop = $(window).scrollTop(); clearTimeout( cce );//终止触发的setTimeout防止重复执行 cce = setTimeout(function() { if (wintop > 200) { if (!$(".bh").hasClass("gb")) { $(".bh").addClass("gb"); $(".bh").animate({ top: 57 }, 500); return false; } } }, 1000); });
<script> var url = 'http://g8up.cn'; var a = document.createElement('a'); a.innerHTML = url; window.open( a.innerHTML ); </script>
1.丢弃小数部分,保留整数部分 parseInt(5/2) 2.向上取整,有小数就整数部分加1 Math.ceil(5/2) 3,四舍五入. Math.round(5/2) 4,向下取整 Math.floor(5/2) Math 对象的方法 FF: Firefox, N: Netscape, IE: Internet Explorer 方法 描述 FF N IE abs(x) 返回数的绝对值 1 2 3 acos(x) 返回数的反余弦值 1 2 3 asin(x) 返回数的反正弦值 1 2 3 atan(x) 以介于 -PI/2 与 PI/2 弧度之间的数值来返回 x 的反正切值 1 2 3 atan2(y,x) 返回从 x 轴到点 (x,y) 的角度(介于 -PI/2 与 PI/2 弧度之间) 1 2 3 ceil(x) 对一个数进行上舍入。 1 2 3 cos(x) 返回数的余弦 1 2 3 exp(x) 返回 e 的指数。 1 2 3 floor(x) 对一个数进行下舍入。 1 2 3 log(x) 返回数的自然对数(底为e) 1 2 3 max(x,y) 返回 x 和 y 中的最高值 1 2 3 min(x,y) 返回 x 和 y 中的最低值 1 2 3 pow(x,y) 返回 x 的 y 次幂 1 2 3 random() 返回 0 ~ 1 之间的随机数 1 2 3 round(x) 把一个数四舍五入为最接近的整数 1 2 3 sin(x) 返回数的正弦 1 2 3 sqrt(x) 返回数的平方根 1 2 3 tan(x) 返回一个角的正切 1 2 3 toSource() 代表对象的源代码 1 4 - valueOf() 返回一个 Math 对象的原始值
我们需要将价格保留两位小数比如12元需要表示为¥12.00,这时就需要使用number_format函数,使用方法为number_format(12,2),如果需要四舍五入,可以使用round函数配合。
number_format
格式化数字字串。
语法: string number_format(float number, int [decimals], string [dec_point], string [thousands_sep]);
传回值: 字串
函式种类: 数学运算
内容说明
本函式用来将浮点参数 number 格式化。若没加参数 decimals 则传回的字串只要整数部份,加了此参数才依参数指定的小数点位数传回。参 数 dec_point 表示小数点的表示方式方法,内定值是 ”.”,若需要转换成其它的小数点就可以在这个参数改掉。参 数 thousands_sep 为整数部份每三位的分隔符号,内定值是 ”,”。本函式最特别的地方就是参数数目,最少要有一个,也就是欲格式化的字 串;也可以有二个或者四个参数,但不能用三个参数。值得注意的是指定小数点的位数之后的数字直接舍弃,没有四舍五入的情形。
描述
number
必需。要格式化的数字。
如果未设置其他参数,则数字会被格式化为不带小数点且以逗号 (,) 作为分隔符。
decimals 可选。规定多少个小数。如果设置了该参数,则使用点号 (.) 作为小数点来格式化数字。
decimalpoint 可选。规定用作小数点的字符串。
separator
可选。规定用作千位分隔符的字符串。
仅使用该参数的第一个字符。比如 "xyz" 仅输出 "x"。
注释:如果设置了该参数,那么所有其他参数都是必需的。
例子:
<?php echo number_format("1000000"); echo number_format("1000000",2); echo number_format("1000000",2,",","."); echo number_format("1000000",2,"*","."); echo number_format("1000000",2,".",""); ?>
输出:
1,000,000
1,000,000.00
1.000.000,00
1.000.000*00
1000000.00
有意思的number_format
number_format(number,decimals,decimalpoint,separator)
有四个参数,
第一个和第二个参数是必须的,第三个和第四个是可选项。但实际测试中第三个和第四个这两个参数必须同时存在,也就是要么都设置,要么都不设置。
没有设置第三个和第四个参数:
Number_format(13526, 2); echo 13,526.00;
如果你将这处理后的数字去累加,则只会得到一个13!。
设置了第三个和第四个参数
Number_format(23125, 2, ‘.',''); echo 23125.00;
这时再对这处理后的数字进行运算的话则会正确执行!
该函数的第三个参数表示 ‘小数点'位置用什么来表示,可以默认 . ,也可以设置成‘,'等其他符号。Ps:但我相信没人会这么干。
第四个则表示每隔 千位时用什么来分割数字。如果没什么特殊要求,又要进行运算的话最好设置为空。
$num = 10.4567; //第一种:利用round()对浮点数进行四舍五入 echo round($num,2); //10.46 //第二种:利用sprintf格式化字符串 $format_num = sprintf("%.2f",$num); echo $format_num; //10.46 //第三种:利用千位分组来格式化数字的函数number_format() echo number_format($num, 2); //10.46 //或者如下 echo number_format($num, 2, '.', ''); //10/46
四舍五入
以下处理结果会四舍五入:
var num =2.446242342; num = num.toFixed(2); // 输出结果为 2.45
不四舍五入
以下处理结果不会四舍五入:
第一种,先把小数边整数:
Math.floor(15.7784514000 * 100) / 100 // 输出结果为 15.77
第二种,当作字符串,使用正则匹配:
Number(15.7784514000.toString().match(/^\d+(?:\.\d{0,2})?/)) // 输出结果为 15.77,不能用于整数如 10 必须写为10.0000
注意:如果是负数,请先转换为正数再计算,最后转回负数
1.丢弃小数部分,保留整数部分
parseInt(5/2)
2.向上取整,有小数就整数部分加1
Math.ceil(5/2)
3,四舍五入.
Math.round(5/2)
4,向下取整
Math.floor(5/2)
<script> var num=22.127456; alert( Math.round(num*100)/100); </script>
功能:将浮点数四舍五入,取小数点后2位,如果不足2位则补0,
这个函数返回的是字符串的格式用法:changeTwoDecimal(3.1415926)返回3.14 changeTwoDecimal(3.1)返回3.10
function changeTwoDecimal(x) { var f_x = parseFloat(x); if (isNaN(f_x)) { alert('function:changeTwoDecimal->parameter error'); return false; } var f_x = Math.round(x*100)/100; return f_x; } 功能:将浮点数四舍五入,取小数点后2位 用法:changeTwoDecimal(3.1415926) 返回 3.14 changeTwoDecimal(3.1475926) 返回 3.15 js保留2位小数(强制) 对于小数点位数大于2位的,用上面的函数没问题,但是如果小于2位的,比如: changeTwoDecimal(3.1),将返回 3.1,如果你一定需要3.10这样的格式,那么需要下面的这个函数: function changeTwoDecimal_f(x) { var f_x = parseFloat(x); if (isNaN(f_x)) { alert('function:changeTwoDecimal->parameter error'); return false; } var f_x = Math.round(x*100)/100; var s_x = f_x.toString(); var pos_decimal = s_x.indexOf('.'); if (pos_decimal < 0) { pos_decimal = s_x.length; s_x += '.'; } while (s_x.length <= pos_decimal + 2) { s_x += '0'; } return s_x; }
1 、tofixed方法
toFixed() 方法可把 Number 四舍五入为指定小数位数的数字。例如将数据Num保留2位小数,则表示为:toFixed(Num);但是其四舍五入的规则与数学中的规则不同,使用的是银行家舍入规则,银行家舍入:所谓银行家舍入法,其实质是一种四舍六入五取偶(又称四舍六入五留双)法。具体规则如下:
简单来说就是:四舍六入五考虑,五后非零就进一,五后为零看奇偶,五前为偶应舍去,五前为奇要进一。
显然这种规则不符合我们平常在数据中处理的方式。为了解决这样的问题,可以自定义去使用Math.round方法进行自定义式 的实现指定保留多少位数据进行处理。
2 、 round方法
round() 方法可把一个数字舍入为最接近的整数。例如:Math.round(x),则是将x取其最接近的整数。其取舍的方法使用的是四舍五入中的方法,符合数学中取舍的规则。对于小数的处理没有那么便捷,但是可以根据不同的要求,进行自定义的处理。
例如:对于X进行保留两位小数的处理,则可以使用Math.round(X * 100) / 100.进行处理。
银行家舍入
所谓银行家舍入法,其实质是一种四舍六入五取偶(又称四舍六入五留双)法。
据说,大部分的编程软件都使用的是这种方法,也算是一种国际标准。 所谓银行家舍入法,其实质是一种四舍六入五取偶(又称四舍六入五留双)法。其规则是:当舍去位的数值小于5时,直接舍去该位;当舍去位的数值大于等于6时,在舍去该位的同时向前位进一;当舍去位的数值等于5时,如果前位数值为奇,则在舍去该位的同时向前位进一,如果前位数值为偶,则直接舍去该位。
简单的说,就是:四舍六入五考虑,五后非零就进一,五后为零看奇偶,五前为偶应舍去,五前为奇要进一
echo "1+5=". 5+1; // 2 echo "1+5=". 1+5; // 6 echo "5+1=". 5+1; // 6 echo "5+1=". 1+5; // 10
可以用隐式类型转换理解
echo intval("1+5=". 5)+1; // 2 echo intval("1+5=". 1)+5; // 6 echo intval("5+1=". 5)+1; // 6 echo intval("5+1=". 1)+5; // 10
intval 会把字符串第一个开始最长数字字符转换为数字
和 c+++++c 一样无聊
.优先计算了,结果字符串
遇到+,尝试转数字类型,转的过程中遇到非数字停止,所以只剩第一个数字+最后一个数字
`echo "5+1=". 1+5;`被解析为`echo ("5+1=". 1)+5;`
前面是个字符串拼接,也就是 `"5+1=1" + 5`,最终`5+1=1`这个字符串,转换成数字,第一个是数字 5,然后+号不是数字,于是被认为是数字 5,加上最后的一个 5,结果是 10。
只能说这代码从语法上就不对。