`

回复:浮点数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  
涨见识了,楼主分析的很透彻,水平就是高!

相关推荐

    CE傻瓜教程四:浮点数.pdf

    2. **模糊搜索**:由于浮点数的精度问题,直接搜索精确值可能找不到匹配的结果。因此,使用模糊搜索(例如,搜索一个范围或者一个近似值)是更实际的方法。在CE中,这通常通过选择"大于"、"小于"或"不等于"等条件来...

    计算机组成原理:浮点数表示及运算.ppt

    计算机组成原理:浮点数表示及运算 浮点数是计算机中用来表示实数的方式,它可以用来表示非常大的数值和非常小的数值。浮点数由三部分组成:符号位、阶码和尾数。符号位表示数的正负,阶码表示数的指数,尾数表示数...

    计算机组成原理:浮点数表示及运算..ppt

    计算机组成原理:浮点数表示及运算 计算机组成原理是计算机科学和电子工程的基础课,它研究计算机的基本组件、原理和设计方法。浮点数表示和运算是计算机组成原理的重要内容之一,本文将对浮点数表示和运算进行详细...

    计算机组成原理:浮点数表示及运算 .ppt

    "计算机组成原理:浮点数表示及运算" 本文将讨论计算机组成原理中的浮点数表示及运算。浮点数是计算机中表示实数的方式之一,它由尾数、阶码和符号位组成。 一、浮点数的表示 浮点数可以表示为 N = Re × m = 2E ...

    第2章 Ⅱ:浮点数-v11

    《浮点数表示与处理》 ...理解浮点数的表示、运算和舍入原则对于计算机科学家和技术人员来说至关重要,这不仅能帮助我们更好地理解计算机如何处理数学问题,也有助于我们在实际编程中避免错误和陷阱。

    Python技法:浮点数取整、格式化和NaN处理.doc

    Python 浮点数处理技法 本文将详细介绍 Python 中浮点数的取整、格式化和 NaN 处理技法。 一、浮点数取整 浮点数取整是将浮点数转换为整数的过程。在 Python 中,有多种方法可以实现浮点数取整。 1. 强转 int ...

    算法-计算浮点数相除的余(信息学奥赛一本通-T1029)(包含源程序).rar

    在信息学奥赛中,计算浮点数相除的余数是一个常见的问题,这涉及到数值计算、浮点数处理和编程技巧。浮点数运算在计算机科学中扮演着至关重要的角色,尤其是在解决数学和工程问题时。这个题目可能是为了帮助参赛者...

    MCS-51单片机实用子程序库

    - **DTOF**,**FTOD**,**BTOF** 和 **FTOB**:定点数、格式化浮点数和BCD浮点数之间的转换。 - **FCOS** 和 **FSIN**:浮点数的余弦和正弦函数,用于三角计算。 - **FATN**:浮点反正切函数。 - **RTOD** 和 **...

    西门子PLC编程指令集.pdf

    浮点算术运算指令用于实现浮点数的算术运算。常见的浮点算术运算指令包括: * ADD_R:实数加法 * SUB_R:实数减法 * MUL_R:实数乘法 * DIV_R:实数除法 * ABS:浮点数绝对值运算 * SQR:浮点数平方 * SQRT:浮点数...

    TIA博途中如何处理浮点数从而得到精确的小数点位数的具体方法.docx

    在TIA博途中,处理浮点数以得到精确的小数点位数是一项常见的任务,尤其在需要对数值进行精确计算或者格式化显示时。本文将详细介绍如何通过编写和使用功能块(FC)来实现这一目标。 首先,创建一个FC块是实现这个...

    双精度浮点数转换

    在计算机科学中,浮点数是一种用于...无论是单精度还是双精度浮点数,了解它们如何在不同表示形式之间转换,以及如何进行实际转换,都能增强我们对计算机内部运作的理解,从而更好地利用这些数据类型来解决实际问题。

    表达式计算(浮点数计算)

    表达式计算(浮点数计算) 本篇文章主要介绍了浮点数计算的实现方法,通过分析给定的代码,解释了浮点数表达式计算的原理和实现细节。 一、浮点数表达式计算概述 浮点数表达式计算是指对浮点数表达式进行计算的...

    cbfi:浮点数和整数之间的转换

    格式:cbfi 0xXXXXXXXX 表示将二进制布局转换为浮点数,只有 16、32、64 位有效。 cbfi XXXXX.XX 表示转换为浮点的二进制布局此外,您可以使用 -d 选项来显示您可能需要的更多信息。 示例:&lt;22&gt;pli[9903]@~/...

    32位定-浮点乘法器设计

    介绍乘法器的设计,可以看看哦,主要是关于定点和浮点的问题

    浮点数的分数表达

    计算机算法设计与分析实验:浮点数的分数表达,可运行源代码,c++

    单片机实用的子程序下载

    - **FPOP**: 浮点数出栈,从堆栈中弹出浮点数。 - **FCMP**: 浮点数代数值比较,比较浮点数大小,但不改变操作数。 - **FABS**: 浮点绝对值函数,获取浮点数的绝对值。 - **FSGN**: 浮点符号函数,返回浮点数的...

    三菱PLC指令

    EBCD2:浮点数-10 进制转换指令,执行浮点数-10 进制转换运算。 EBIN2:浮点数-2 进制转换指令,执行浮点数-2 进制转换运算。 EADD2:浮点数加法指令,执行浮点数加法运算。 ESUB2:浮点数减法指令,执行浮点数...

    三菱FX系列plc指令详解.docx

    三菱FX系列PLC指令集包括输入输出指令、计时器指令、比较指令、数据传送指令、移位指令、逻辑运算指令、数学运算指令、浮点数运算指令和其他指令等。这些指令可以满足不同的应用需求,实现自动化控制系统的设计和...

    计算机组成原理第六章答案 .doc

    本章节主要讲解了计算机组成原理中数字表示和浮点数表示的相关知识点。 1. 数字表示: 在计算机中,数字可以用原码、反码、补码和移码四种方式来表示。其中,原码是指用符号位和数值位组成的代码,反码是原码的反转...

    c语言输出格式.pdf

    对e、g、f类当结果有小数时才给出小数点。 3. 格式字符串 格式字符串是由多个部分组成的,包括: * 标志:-、+、空格等 * 输出最少宽度:m * 精度:n * 长度:h、l等 * 类型:%d、%f、%s等 例如:%m.ns表示输出m...

Global site tag (gtag.js) - Google Analytics