Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

浮点数加减问题 #1

Open
zmmbreeze opened this issue Aug 31, 2016 · 0 comments
Open

浮点数加减问题 #1

zmmbreeze opened this issue Aug 31, 2016 · 0 comments

Comments

@zmmbreeze
Copy link
Owner

zmmbreeze commented Aug 31, 2016

以前的旧文章,转移到这里

今天花了一整天的时间复习二进制相关知识,在这里写下这篇blog作为总结!

为什么“0.1+0.2=0.30000000000000004”?

首先声明这不是bug,原因在与十进制到二进制的转换导致的精度问题!其次这几乎出现在很多的编程语言中:C/C++,Java,Javascript中,准确的说:“使用了IEEE 754浮点数格式”来存储浮点类型(float 32,double 64)的任何编程语言都有这个问题!

简要介绍下IEEE 754浮点格式:它用科学记数法以底数为2的小数来表示浮点数。IEEE浮点数(共32位)用1位表示数字符号,用8为表示指数,用23为来表示尾数(即小数部分)。此处指数用移码存储,尾数则是原码(没有符号位)。之所以用移码是因为移码的负数的符号位为0,这可以保证浮点数0的所有位都是0。双精度浮点数(64位),使用1位符号位、11位指数位、52位尾数位来表示。

因为科学记数法有很多种方式来表示给定的数字,所以要规范化浮点数,以便用底数为2并且小数点左边为1的小数来表示(注意是二进制的,所以只要不为0则一定有一位为1),按照需要调节指数就可以得到所需的数字。例如:十进制的1.25 => 二进制的1.01 => 则存储时指数为0、尾数为1.01、符号位为0.(十进制转二进制

回到开头,为什么“0.1+0.2=0.30000000000000004”?首先声明这是javascript语言计算的结果(注意Javascript的数字类型是以64位的IEEE 754格式存储的)。正如同十进制无法精确表示1/3(0.33333...)一样,二进制也有无法精确表示的值。例如1/10。64位浮点数情况下:

十进制0.1
=> 二进制0.00011001100110011...(循环0011)
=>尾数为1.1001100110011001100...1100(共52位,除了小数点左边的1),指数为-4(二进制移码为00000000010),符号位为0
=> 存储为:0 00000000100 10011001100110011...11001
=> 因为尾数最多52位,所以实际存储的值为0.00011001100110011001100110011001100110011001100110011001

十进制0.2
=> 二进制0.0011001100110011...(循环0011)
=>尾数为1.1001100110011001100...1100(共52位,除了小数点左边的1),指数为-3(二进制移码为00000000011),符号位为0
=> 存储为:0 00000000011 10011001100110011...11001
因为尾数最多52位,所以实际存储的值为0.00110011001100110011001100110011001100110011001100110011

两者相加:
0.00011001100110011001100110011001100110011001100110011001 +  0.00110011001100110011001100110011001100110011001100110011 = 0.01001100110011001100110011001100110011001100110011001100
转换成10进制之后得到:0.30000000000000004!

浮点数中的特殊数字

除了一般范围内的数字之外,还有一些特殊数字:无穷大、负无穷大、-0和NaN(“代表不是数字”)。造成了如下一些特殊情况:

public class FloatTest {
    public static void main(String[] args) {
        System.out.println(0.1f+0.2f); //0.3
        System.out.println(0.1d+0.2d); //0.30000000000000004
        System.out.println(Math.sqrt(-1.0)); //NaN
        System.out.println(0.0 / 0.0);//NaN
        System.out.println(1.0 / 0.0);//Infinity
        System.out.println(-1.0 / 0.0);//-Infinity
        System.out.println(0.0 / 0.0 + 1.0);//NaN + 1.0 = NaN
        System.out.println(1.0 / 0.0 + 1.0);//无穷大 + 1.0 = Infinity
        System.out.println(1.0 / 0.0 + 1.0 / 0.0);//无穷大 + 无穷大 = Infinity
        System.out.println(0.0 / 0.0 > 1.0);//NaN > 1.0 = false
        System.out.println(0.0 / 0.0 == 1.0);//NaN == 1.0 = false
        System.out.println(0.0 / 0.0 < 1.0);//NaN < 1.0 = false
        System.out.println(0.0 / 0.0 == 0.0 / 0.0);//NaN == NaN = false
        System.out.println(0.0 == -0.01); //false
    }
}

更精确的计算

既然一般的浮点数计算有这么多问题,那么如何实现更精确的计算呢?Java中提供了BigDecimal类实现基于十进制的浮点数计算。在Javascript 2(目前浏览器不支持)中提供一种use decimal;实现十进制浮点数计算:

{
    use decimal;
    var a = 0.1; // a is a decimal
    var b = 0.2; // b is a decimal
    var c = a + b; // c is a decimal (0.3)
}
var d = 0.1 + 0.2; // d is a double (0.30000000000000004)
var a = 0.1m; // a is a decimal
var b = 0.2m; // b is a decimal
var c = a + b; // c == 0.3m

C#也支持如上的m操作符实现十进制浮点数计算。

2013/07/12 更新:Javascript 目前有MathJs这样的第三方库可以实现精确的计算。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant