`

回复:浮点数0.57 0.58 造出的坑爹问题

阅读更多

今天看到 vb2005xu 提到了一个问题  浮点数0.57 0.58 造出的坑爹问题 

parseInt(0.59*100)  // 59
parseInt(0.58*100)  // 57
parseInt(0.57*100)  // 56
parseInt(0.56*100)  // 56

 为什么会这样呢?随后又举了 PHP 的例子,结果还是一样的结果,只是函数换成了 intval,于是 vb2005xu 猜想,是不是 python 也这样呢。 
这个问题看似奇特,其实还是浮点数的精度的问题,我以前写的这篇文章:代码之谜(五)- 浮点数(谁偷了你的精度?) 
0.58*100 的结果是什么?其实大家试试就知道了,并不是想象中的 58,而是 57.99999999999999。为什么?看我上面的文章。 
是 js 或者 php 这些动态语言的怪癖吗?C语言,java会这样吗? 
其实 0.58*100 = 57.99999999999999 是不局限于任何语言的,是 IEEE 规定的浮点数的运算标准。一般情况下,57.99999999999999 会四舍五入到 58。需要注意的是,浮点数的四舍五入和咱们普通的数学里面的也是不同的,浮点数遇到 5 后,不一定总是入,有时也舍,具体细节不多解释了。
为什么结果是 57 呢,主要是因为 parseInt 和 intval 函数。他们的规则是,从第一个数字开始,知道遇到不是数字的字符,结束。
所以 
parseInt("012") 结果是 10 (不要惊讶,0开头的数字是八进制)
parseInt("12abc") 结果是 12 (不解释)
parseInt("12.123") 结果是 12
12e5 是多少呢?科学计数法,结果是 1200000(12后面五个0)
parseInt("12e5") 的结果呢?结果是12,因为e字符不是数字,所以后面的都忽略了。
parseInt("abc") 这个呢?结果是0?如果是0的话,你让 parseInt("0abc") 情何以堪啊!结果是 NaN (Not a Number)。 
-----------------2013-05-09 17:12 补充-----------------------------------------
vb2005xu 写道
此处的精度问题 为什么 只有0.57/0.58 这两个有问题 而 0.59 0.56 等却没有问题呢
 由于时间问题,临近下班了,所以不细解释了。还是这篇文章,代码之谜(五)- 浮点数(谁偷了你的精度?),仔细品味一下。如果把它们写出2进制浮点数,就明白了。其实一个令人震惊的事实就是,99%的数不能够被精确的表示为浮点数。

0.56*100 56.00000000000001
0.57*100 56.99999999999999
0.58*100 57.99999999999999
0.59*100 59

看到上面的表格,好像 0.59 可以精确表示一样,其实不然,首先,0.59的末尾是9,意味着他不可能被转换成有限二进制小数(why?)。肯定是一个循环小数,如果循环节超过了浮点数的尾数,那么就给人一种可以精确表示的假象。(设计到很多公式,就不写了,以后专门写博客讨论)。

运行: 0.59*1e71
结果: 5.9e+70

运行: 0.59*1e72
结果: 5.899999999999999e+71

看出端倪来了吗?
75
36
分享到:
评论
19 楼 thihy 2013-08-03  
很简单,所有金钱*100存储就可以了
求求你帮帮我 写道
那JS精度问题到底要怎么解决呢?银行的系统不敢这么干吧?他们少算一毛钱都是大事啊?

18 楼 justjavac 2013-08-02  
求求你帮帮我 写道
那JS精度问题到底要怎么解决呢?银行的系统不敢这么干吧?他们少算一毛钱都是大事啊?

https://github.com/MikeMcl/bignumber.js/
17 楼 求求你帮帮我 2013-08-02  
那JS精度问题到底要怎么解决呢?银行的系统不敢这么干吧?他们少算一毛钱都是大事啊?
16 楼 rensanning 2013-05-22  
thihy 写道
thihy 写道
rensanning 写道
估计很多人也会奇怪这个问题:
public static void main(String[] args) throws ParseException {
    SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
    String str3 = "1927-12-31 23:54:07";  
    String str4 = "1927-12-31 23:54:08";  
    Date sDt3 = sf.parse(str3);  
    Date sDt4 = sf.parse(str4);  
    long ld3 = sDt3.getTime() /1000;  
    long ld4 = sDt4.getTime() /1000; 
    System.out.println(ld4-ld3);
}

引用

353

long不会产生溢出。这里不是程序的问题,而是时间被回拨了。见:http://stackoverflow.com/questions/6841333/why-is-subtracting-these-two-times-in-1927-giving-a-strange-result/6841479


写快了,不是“long不会溢出”,而是“long没有精度问题”。

嗯,正解,这是stackoverflow.com上Upvote很高的一个问题。
15 楼 thihy 2013-05-21  
thihy 写道
rensanning 写道
估计很多人也会奇怪这个问题:
public static void main(String[] args) throws ParseException {
    SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
    String str3 = "1927-12-31 23:54:07";  
    String str4 = "1927-12-31 23:54:08";  
    Date sDt3 = sf.parse(str3);  
    Date sDt4 = sf.parse(str4);  
    long ld3 = sDt3.getTime() /1000;  
    long ld4 = sDt4.getTime() /1000; 
    System.out.println(ld4-ld3);
}

引用

353

long不会产生溢出。这里不是程序的问题,而是时间被回拨了。见:http://stackoverflow.com/questions/6841333/why-is-subtracting-these-two-times-in-1927-giving-a-strange-result/6841479


写快了,不是“long不会溢出”,而是“long没有精度问题”。
14 楼 thihy 2013-05-21  
rensanning 写道
估计很多人也会奇怪这个问题:
public static void main(String[] args) throws ParseException {
    SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
    String str3 = "1927-12-31 23:54:07";  
    String str4 = "1927-12-31 23:54:08";  
    Date sDt3 = sf.parse(str3);  
    Date sDt4 = sf.parse(str4);  
    long ld3 = sDt3.getTime() /1000;  
    long ld4 = sDt4.getTime() /1000; 
    System.out.println(ld4-ld3);
}

引用

353

long不会产生溢出。这里不是程序的问题,而是时间被回拨了。见:http://stackoverflow.com/questions/6841333/why-is-subtracting-these-two-times-in-1927-giving-a-strange-result/6841479
13 楼 rensanning 2013-05-20  
估计很多人也会奇怪这个问题:
public static void main(String[] args) throws ParseException {
    SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
    String str3 = "1927-12-31 23:54:07";  
    String str4 = "1927-12-31 23:54:08";  
    Date sDt3 = sf.parse(str3);  
    Date sDt4 = sf.parse(str4);  
    long ld3 = sDt3.getTime() /1000;  
    long ld4 = sDt4.getTime() /1000; 
    System.out.println(ld4-ld3);
}

引用

353
12 楼 justjavac 2013-05-13  
冷静 写道
Double a=0.59d*100d;
Double b=0.58d*100d;
Double c=0.57d*100d;
Double d=0.56d*100d;

System.out.println("a:"+a.intValue());
System.out.println("b:"+b.intValue());
System.out.println("c:"+c.intValue());
System.out.println("d:"+d.intValue())

运行结果:
a:59
b:57
c:56
d:56


Double.intValue() 是进行的四舍五入。
11 楼 冷静 2013-05-12  
Double a=0.59d*100d;
Double b=0.58d*100d;
Double c=0.57d*100d;
Double d=0.56d*100d;

System.out.println("a:"+a.intValue());
System.out.println("b:"+b.intValue());
System.out.println("c:"+c.intValue());
System.out.println("d:"+d.intValue())

运行结果:
a:59
b:57
c:56
d:56
10 楼 thihy 2013-05-11  
justjavac 写道
thihy 写道
有个问题想请教一下:
0.58*100 = 57.99999999999999
但是
0.58*1000 = 580

这是为啥呢?


先来个小插曲吧,1/9801的结果是多少呢?(9801是99的平方)。

结果是 0.00010203040506070809……909192939495969799…… 它的循环节是从00到99,中间没有98。

0.58 转换成二进制位 0.10010100011110101110000101000111…… 最终也会循环,0.58也就是58/100,虽然在十进制中可以精确表示为小数,但是在二进制下是循环的。

因为0.58的表示是无限循环的,但是我们的计算机位长是有限的,所以我们就得截取,当把截取后的数再转换回十进制,结果是 0.57999999999999998223643160599749

用这个数代替0.58运算一下。

0.57999999999999998223643160599749 * 10 = 5.7999999999999998223643160599749
如果在浏览器控制台输入此表达式,会得到5.8

在计算机中,运算的过程都是二进制,所以 0.58 在转换成二进制的时候,丢失了精度,因为0.58在二进制中是无限循环小数,必须得截取。在表示为规格浮点数的时候,32位或者64位,或者128位,不管表示为多少位,都得丢失精度。

第二步,乘以10。得到一个二进制浮点数结果,当把这个结果转换成十进制的时候,有可能会丢失精度。


在大致阅读了IEEE的规范之后,感觉好像里面对于浮点的运算(如何Round)没有强制的要求。然后观察到不同的语言都返回相同的值,联想到是不是这些程序其实都是返回的FPU的结果。而FPU又与CPU有很大的关联。在Intel的某篇日志中说,Intel ??型号是使用80bit的扩展浮点数来计算数据的,但是其余的相关细节就不太清楚了。有没有可能不同的CPU(或FPU)的平台上会返回不同的值呢?
9 楼 justjavac 2013-05-11  
thihy 写道
有个问题想请教一下:
0.58*100 = 57.99999999999999
但是
0.58*1000 = 580

这是为啥呢?


先来个小插曲吧,1/9801的结果是多少呢?(9801是99的平方)。

结果是 0.00010203040506070809……909192939495969799…… 它的循环节是从00到99,中间没有98。

0.58 转换成二进制位 0.10010100011110101110000101000111…… 最终也会循环,0.58也就是58/100,虽然在十进制中可以精确表示为小数,但是在二进制下是循环的。

因为0.58的表示是无限循环的,但是我们的计算机位长是有限的,所以我们就得截取,当把截取后的数再转换回十进制,结果是 0.57999999999999998223643160599749

用这个数代替0.58运算一下。

0.57999999999999998223643160599749 * 10 = 5.7999999999999998223643160599749
如果在浏览器控制台输入此表达式,会得到5.8

在计算机中,运算的过程都是二进制,所以 0.58 在转换成二进制的时候,丢失了精度,因为0.58在二进制中是无限循环小数,必须得截取。在表示为规格浮点数的时候,32位或者64位,或者128位,不管表示为多少位,都得丢失精度。

第二步,乘以10。得到一个二进制浮点数结果,当把这个结果转换成十进制的时候,有可能会丢失精度。
8 楼 justjavac 2013-05-11  
thihy 写道
有个问题想请教一下:
0.58*100 = 57.99999999999999
但是
0.58*1000 = 580

这是为啥呢?


0.58 * 10     5.8
0.58 * 100    57.99999999999999
0.58 * 1e+3   580
0.58 * 1e+4   5800
0.58 * 1e+5   57999.99999999999
0.58 * 1e+6   580000
0.58 * 1e+7   5800000
0.58 * 1e+8   57999999.99999999
0.58 * 1e+9   580000000
0.58 * 1e+10  5800000000
0.58 * 1e+11  57999999999.99999
0.58 * 1e+12  580000000000


很有取的规律,格式也很好看。为什么呢?【关于此,我确信已发现了一种美妙的证法,可惜这里空白的地方太小,写不下。】

呵呵。玩笑。这个不是三言两语可以解释清楚的,回头我会专门写一篇博客来讨论浮点数的问题。
7 楼 thihy 2013-05-10  
有个问题想请教一下:
0.58*100 = 57.99999999999999
但是
0.58*1000 = 580

这是为啥呢?
6 楼 justjavac 2013-05-10  
sean-9 写道
前两天碰到的同样的问题,
我用的是java,
最终解决方案, 用double.toString() + Bigdecimal(String)
解决。

很好的办法。
double是用浮点数来表示小数,BigDecimal是用定点数表示小数。
一个坑就是 BigDecimal(String) 参数要用字符串,而别用浮点数。
5 楼 sean-9 2013-05-10  
前两天碰到的同样的问题,
我用的是java,
最终解决方案, 用double.toString() + Bigdecimal(String)
解决。
4 楼 smartcatii 2013-05-10  
LZ这个问题比较好理解。parseInt转换时是直接去掉小数点的。

浮点数运算后结果为

0.59*100 >= 59
0.58*100 < 58
0.57*100 < 57
0.56*100 >= 56

通常都是这样进行转换的 Math.round(0.57 * 100)


难理解的是如下问题(Java语言),为什么float和double得出的结果会不一样,至少小数点后几位float和double都应该一样才对。LZ有空时去找找答案吧

@Test
    public void test_01() {
        Double d = 1297.61D;
        int result = (int) (d * 100);
        System.out.println(result);

        result = (int) (d * 100D);
        System.out.println(result);
    }

    @Test
    public void test_02() {
        float d = 1297.61f;
        int result = (int) (d * 100);
        System.out.println(result);

        result = (int) (d * 100f);
        System.out.println(result);
    }

3 楼 完美的休止符 2013-05-10  
“为什么结果是 57 呢,主要是因为 parseInt 和 intval 函数。他们的规则是,从第一个数字开始,知道遇到不是数字的字符,结束。


错别字了。
2 楼 coffeescript 2013-05-09  
深入浅出,分析的不错,受教了。
1 楼 steafler 2013-05-09  
涨见识了,楼主分析的很透彻,水平就是高!

相关推荐

Global site tag (gtag.js) - Google Analytics