求js大牛解惑!一个很有趣的问题: "0.14"*100 = 14.000000000000002 " , 而 0.13

1个回答

  • 这个问题挺复杂的,以前看过一次,但没太看懂,这次整理了一下.有兴趣的同学可以看看.

    首先打印出0.14和0.14*100的二进制(程序见参考):

    0.14 : 0 01111111100 0001111010111000010100011110101110000101000111101100

    0.14*100 : 0 10000000010 1100000000000000000000000000000000000000000000000001

    14.0 : 0 10000000010 1100000000000000000000000000000000000000000000000000

    我们可以看到0.14的小数部分出现循环,就像10/3 = 1.33333…一样,所以0.14是有精度丢失的,

    比较0.14*100和14.0,0.14*100多了最后一位1.

    Java遵循IEEE 754标准来支持浮点数的操作,结合浮点值0.14,我们来看一下,1) 0.14的二进制格式

    0 01111111100 0001111010111000010100011110101110000101000111101100.

    根据IEEE 754标准,

    注:S:符号(0正1负),E,指数,M小数部分.

    对0.14格式化后的结果是:

    S(1位) E(11位) M (52位)

    0 01111111100 0001111010111000010100011110101110000101000111101100

    根据计算公式:

    我们可以得到e = 01111111100 – 1023 = 1020 – 1023 = -3

    m = 1 (隐藏位) + 0.0001111010111000010100011110101110000101000111101100 = 1 +

    Long.valueOf("0001111010111000010100011110101110000101000111101100", 2)/ (Math.pow(2, 52) – 1)

    = 1.12000000000000013

    n = 1.12000000000000013 *2^-3= 1.12000000000000013/8 约 0.14

    接下来,第二个问题,2)为什么0.14 * 100 输出14.000000000000002?

    由上可知0.14是不精确的,乘100只会扩大这个不精确度.具体的计算过程如下:

    100的浮点二进制:

    0 10000000101 1001000000000000000000000000000000000000000000000000

    跟据浮点乘法规则指数相加,小数相乘,得到

    0.14 * 100 =

    2^(6-3) //100的指数是6,0.14的指数是-3

    *

    (1. 1001000000000000000000000000000000000000000000000000*

    1. 0001111010111000010100011110101110000101000111101100) //小数部分

    = 2^(6-3) * (1 + 0.1001000000000000000000000000000000000000000000000000 +

    0.0001111010111000010100011110101110000101000111101100 +

    0. 1001000000000000000000000000000000000000000000000000 * 0. 0001111010111000010100011110101110000101000111101100

    //方便计算,分解乘数(同1.1 * 1.1 = 1 + 0.1 * 1 + 1 * 0.1 + 0.1*0.1)

    这部分我用计算器计算,貌似精度丢失更严重,但可以得知小数部分依然是循环,而不能精确表达.同时,通过查看JDK中Double.toString(d)方法,我们可以看到SUN实现的浮点输出方法.所以最后打印在页面的是14.000000000000002而不是14.0.

    故,浮点是用有限二进位接近表达一个数值,不精确是常态,使用要慎重

    参考(强烈推荐)

    IEEE 754 浮点数的表示精度探讨

    http://www.***.com/bossin/archive/2007/04/08/704567.html

    解读IEEE标准754:浮点数表示

    http://www.***.org/bbs/thread262207.html

    浮点乘法计算

    http://www.cs.umd.edu/class/spring2003/cmsc311/Notes/BinMath/multFloat.html

    打印DOUBLE二进制方法:

    public class DoubleTest {

    @Test

    public void test(){

    System.out.println("0.5 : " + this.dumpDouble(0.5));

    System.out.println("0.14 : " + this.dumpDouble(0.14));

    System.out.println("0.14*100 : " + this.dumpDouble(0.14 * 100));

    System.out.println("14.0 : " + this.dumpDouble(14.0));

    System.out.println("100 : " + this.dumpDouble(100));

    Assert.assertEquals("100.0%", getPercentage(0.9999, 2));

    Assert.assertEquals("100%", getPercentage(0.9999, 1));

    Assert.assertEquals("99.9%", getPercentage(0.999, 2));

    Assert.assertEquals("10.1%", getPercentage(0.101, 2));

    Assert.assertEquals("10.2%", getPercentage(0.1015, 2));

    Assert.assertEquals("11.0%", getPercentage(0.1095, 2));

    Assert.assertEquals("0.11%", getPercentage(0.0011, 3));

    final int TOTAL = 100000;

    double[] data = new double[TOTAL];

    for(int i = 0; i < TOTAL; i++ ){

    data[i] = Math.random();

    }

    //通过FORMAT,获得百分比,忽略精度

    DecimalFormat f = new DecimalFormat("0.##'.0'%");

    long start = System.currentTimeMillis();

    for(int i = 0; i < TOTAL; i++ ){

    f.format(data[i]);

    }

    long end = System.currentTimeMillis();

    System.out.println("0 time: " + (end - start));

    //基于字符计算百分比

    start = System.currentTimeMillis();

    for(int i = 0; i < TOTAL; i++ ){

    this.getPercentage(data[i], 2);

    }

    end = System.currentTimeMillis();

    System.out.println("1 time: " + (end - start));

    }

    public String dumpDouble(double d) {

    StringBuilder b = new StringBuilder();

    String bString = Long.toBinaryString(Double.doubleToLongBits(d));

    int fillZeros = 64 - bString.length();

    for(int i = 0; i < fillZeros ; i++){

    if(i == 1){

    b.append(' ');

    }

    b.append('0');

    }

    for(int i = 0; i < bString.length(); i++ ){

    if((i + fillZeros) == 1 || (i + fillZeros) == 12){

    b.append(' ');

    }

    b.append(bString.charAt(i));

    }

    return b.toString();

    }

    char[] next = {'1', '2', '3', '4', '5', '6', '7', '8', '9', '0',};

    public String getPercentage(double d, int precision){

    StringBuilder b = new StringBuilder(Double.toString(d * 100));

    int dot = b.indexOf(".");

    if(dot > 0 && dot + precision < b.length()){

    char c = b.charAt(dot + precision);

    //四舍五入

    if(c > '4'){

    int j = dot + precision - 1;

    if(j == dot) {

    //跳过小数点

    j --;

    }

    char n = next[b.charAt(j) - '0'];

    b.setCharAt(j, n);

    //向前进1

    while(n == '0' && j > 0){

    j --;

    if(j == dot) {

    //跳过小数点

    j --;

    }

    //+1

    n = next[b.charAt(j) - '0'];

    b.setCharAt(j, n);

    }

    if(j == 0){

    b.insert(0, 1);

    dot++;

    if(precision == 1){

    b.setCharAt(dot, '%');

    return b.substring(0, dot + 1);

    }else{

    b.setCharAt(dot + precision, '%');

    return b.substring(0, dot + precision + 1);

    }

    }

    }

    b.setCharAt(dot + precision, '%');

    return b.substring(0, dot + precision + 1);

    }

    b.append("%");

    return b.toString();

    }