文档章节

2.0 - 1.1和1.0 - 0.1

xionghuiCoder
 xionghuiCoder
发布于 2017/01/09 19:06
字数 1195
阅读 137
收藏 1

本文主要来介绍下计算机计算浮点数的问题;计算机使用二进制来计算和存储小数,由于某些小数不能用二进制准确的表示,所以往往有误差。

参考如下测试用例:

/**
 * 测试小数计算问题
 *
 * @author xionghui
 * @email xionghui.xh@alibaba-inc.com
 * @since 1.0.0
 */
public class DoubleTest {

  public static void main(String[] args) {
    double result = 2.0 - 1.1;
    // 存在精度问题
    System.out.println("2.0 - 1.1 = " + result);
  }
}

输出结果是:

2.0 - 1.1 = 0.8999999999999999

我们知道出现这种问题是因为计算机的二进制不能精确表示1.1;

解决方案可参考《Java解惑》:

1、先把小数转换为整数计算,计算完成再转回小数;

2、使用BigDecimal进行计算。

如果我们再进一步,会发现以下问题:

/**
 * 测试小数计算问题
 *
 * @author xionghui
 * @email xionghui.xh@alibaba-inc.com
 * @since 1.0.0
 */
public class DoubleTest {

  public static void main(String[] args) {
    double result = 2.0 - 1.1;
    // 存在精度问题
    System.out.println("2.0 - 1.1 = " + result);

    result = 1.0 - 0.1;
    // 不存在精度问题
    System.out.println("1.0 - 0.1 = " + result);
  }
}

输出结果出乎意外:

2.0 - 1.1 = 0.8999999999999999
1.0 - 0.1 = 0.9

请注意“2.0 - 1.1”出现了精度问题;但是“1.0 - 0.1”却没问题?

理论上计算机不能用二进制计算和表示1.1,同样也不能用二进制计算和表示0.1,那为什么“2.0 - 1.1”会出现精度问题,而“1.0 - 0.1”却没问题?

于是我们先看一下计算机是如何表示小数的二进制的,参考下面测试用例:

/**
 * 测试小数计算问题
 *
 * @author xionghui
 * @email xionghui.xh@alibaba-inc.com
 * @since 1.0.0
 */
public class DoubleTest {

  public static void main(String[] args) {
    calBinary(2.0);
    calBinary(1.1);
    calBinary(1.0);
    calBinary(0.1);
  }

  /**
   * 打印double的二进制表示
   */
  private static void calBinary(double d) {
    System.out.print(d + "的二进制: ");
    long value = Double.doubleToRawLongBits(d);
    // 计算double的64位二进制编码,此处注意java默认是Big endian
    for (int i = 63; i >= 0; i--) {
      int v = (int) ((value >> i) & (0x1));
      System.out.print(v);
    }
    System.out.println();
  }
}

输出结果是:

2.0的二进制: 0100000000000000000000000000000000000000000000000000000000000000
1.1的二进制: 0011111111110001100110011001100110011001100110011001100110011010
1.0的二进制: 0011111111110000000000000000000000000000000000000000000000000000
0.1的二进制: 0011111110111001100110011001100110011001100110011001100110011010

java编译器是遵照IEEE制定的浮点数表示法来进行float,double运算的。这种结构是一种科学计数法,用符号、指数和尾数来表示,底数定为2(即把一个浮点数表示为尾数乘以2的指数次方再添上符号)。下面是具体的参数:

  符号位数 阶码位数 尾数位数 总位数
float 1 8 23 32
double 1 11 52 64

这里我们只关注double类型(float类似),double数值的计算方法是:

  1. “符号位”0表示正数,1表示负数;
  2. 计算“阶码位”的值,然后减去1023,结果记为exp;
  3. “尾数位”前面补“1.”,然后乘以2的exp次方;

我们来看2.0,1.1,1.0,0.1四个小数的表示:

  符号位 阶码位 尾数位 计算结果
2.0 0 10000000000 0000000000000000000000000000000000000000000000000000 +1.0000000000000000000000000000000000000000000000000000*2的1次方
1.1 0 01111111111 0001100110011001100110011001100110011001100110011010 +1.0001100110011001100110011001100110011001100110011010*2的0次方
1.0 0 01111111111 0000000000000000000000000000000000000000000000000000 +1.0000000000000000000000000000000000000000000000000000*2的0次方
0.1 0 01111111011 1001100110011001100110011001100110011001100110011010 +1.1001100110011001100110011001100110011001100110011010*2的-4次方

好,下面来模拟下计算机来通过二进制计算“2.0 - 1.1”和“1.0 - 0.1”:

首先把上表的计算结果的指数都转为2的0次方(即1):

2.0 = 10.0000000000000000000000000000000000000000000000000000
1.1 = 01.0001100110011001100110011001100110011001100110011010

1.0 = 1.00000000000000000000000000000000000000000000000000000000
0.1 = 0.00011001100110011001100110011001100110011001100110011010

然后通过以下测试用例来计算“2.0 - 1.1”和“1.0 - 0.1”:

/**
 * 测试小数计算问题
 *
 * @author xionghui
 * @email xionghui.xh@alibaba-inc.com
 * @since 1.0.0
 */
public class DoubleTest {

  public static void main(String[] args) {
    System.out.print("2.0 - 1.1 = ");
    subtractBinary("10.0000000000000000000000000000000000000000000000000000",
        "01.0001100110011001100110011001100110011001100110011010");

    System.out.print("1.0 - 0.1 = ");
    subtractBinary("1.00000000000000000000000000000000000000000000000000000000",
        "0.00011001100110011001100110011001100110011001100110011010");
  }

  /**
   * 计算二进制结果
   */
  private static void subtractBinary(String subtrahend, String minuend) {
    List<Character> result = new ArrayList<Character>();
    int tmp = 0;
    for (int i = subtrahend.length() - 1; i >= 0; i--) {
      char subtrahendChar = subtrahend.charAt(i);
      if (subtrahendChar == '.') {
        result.add('.');
        continue;
      }
      char minuendChar = minuend.charAt(i);
      if (tmp == 1) {
        minuendChar += 1;
        tmp = 0;
      }
      if (subtrahendChar < minuendChar) {
        subtrahendChar += 2;
        tmp = 1;
      }
      result.add((char) (subtrahendChar - minuendChar));
    }
    for (int i = result.size() - 1; i >= 0; i--) {
      char value = result.get(i);
      if (value == '.') {
        System.out.print(value);
        continue;
      }
      System.out.print((int) result.get(i));
    }
    System.out.println();
  }
}

计算结果为:

2.0 - 1.1 = 00.1110011001100110011001100110011001100110011001100110
1.0 - 0.1 = 0.11100110011001100110011001100110011001100110011001100110

再把计算的二进制结果转换为double数值,测试用例如下:

/**
 * 测试小数计算问题
 *
 * @author xionghui
 * @email xionghui.xh@alibaba-inc.com
 * @since 1.0.0
 */
public class DoubleTest {

  public static void main(String[] args) {
    System.out.print("2.0 - 1.1 = ");
    showDouble("00.1110011001100110011001100110011001100110011001100110");

    System.out.print("1.0 - 0.1 = ");
    showDouble("0.11100110011001100110011001100110011001100110011001100110");
  }

  /**
   * 计算二进制结果
   */
  private static void showDouble(String binary) {
    int index = binary.indexOf('.');
    if (index != -1) {
      binary = binary.substring(index + 1);
    }
    BigDecimal result = new BigDecimal("0.0");
    BigDecimal tmp = new BigDecimal("1.0");
    BigDecimal halfOne = new BigDecimal("0.5");
    for (int i = 0, len = binary.length(); i < len; i++) {
      tmp = tmp.multiply(halfOne);
      if (binary.charAt(i) == '1') {
        result = result.add(tmp);
      }
    }
    System.out.println(result);
  }
}

测试结果为:

2.0 - 1.1 = 0.8999999999999999111821580299874767661094665527343750
1.0 - 0.1 = 0.89999999999999999444888487687421729788184165954589843750

可以看到“1.0 - 0.1”也是有误差的;

最后把结果转换为double数值,测试用例如下:

/**
 * 测试小数计算问题
 *
 * @author xionghui
 * @email xionghui.xh@alibaba-inc.com
 * @since 1.0.0
 */
public class DoubleTest {

  public static void main(String[] args) {
    double result = 0.8999999999999999111821580299874767661094665527343750;
    System.out.println("2.0 - 1.1 = " + result);

    result = 0.89999999999999999444888487687421729788184165954589843750;
    System.out.println("1.0 - 0.1 = " + result);
  }
}

测试结果为:

2.0 - 1.1 = 0.8999999999999999
1.0 - 0.1 = 0.9

好了,可以看到“1.0 - 0.1”的计算结果截取时进位了,所以是0.9;而“2.0 - 1.1”的结算结果截取时没有进位,所以是0.8999999999999999。

 

© 著作权归作者所有

共有 人打赏支持
xionghuiCoder
粉丝 86
博文 34
码字总数 31340
作品 4
海淀
程序员
私信 提问
java byte数组 相关知识点

参考链接: http://www.cnblogs.com/aipan/p/6341346.html 下面是Java.util.Random()方法摘要 1.protected int next(int bits):生成下一个伪随机数。 2.boolean nextBoolean():返回下一个伪......

wangtenfee
2017/05/23
0
0
python编写execl文件方法

1. 安装xlwt模块 1)下载地址 https://pypi.python.org/pypi/xlwt#downloads 2)安装方法 tar zxvf xlwt-1.1.2.tar.gz cd xlwt-1.1.2 python setup.py install 2. 目标execl文件 ………… 3.......

gongniue
2016/08/04
0
0
Maven 小技巧之 自动更新你的jar包

在maven中我们经常像下面这样引用一个依赖: <dependency> <groupId>wonderful-inc</groupId> <artifactId>dream-library</artifactId> <version>1.2.3</version></dependency> 我们指定了固......

熊二哈
2016/03/03
34
0
JAVAEE5 JAVAEE6 JAVAEE7规范列表

JAVAEE7 JSR 342 Web Application Technologies: Java API for WebSocket JSR 356 Java API for JSON Processing JSR 353 Java Servlet 3.1 JSR 340 JavaServer Faces 2.2 JSR 344 Expressio......

anranran
2016/12/30
0
0
【初学者】openGL的物体转速不一样

就是如下代码里面 MyDisplay()中的第二段for循环的内容执行与否 为什么会影响第一段的物体的转速?不明白 如果把第二段for循环注释了,就会发现转速变了。。。 #include #include #include #...

Michaelwpf
2016/05/17
134
0

没有更多内容

加载失败,请刷新页面

加载更多

mybatis缓存的装饰器模式

一般在开发生产中,对于新需求的实现,我们一般会有两种方式来处理,一种是直接修改已有组件的代码,另一种是使用继承方式。第一种显然会破坏已有组件的稳定性。第二种,会导致大量子类的出现...

算法之名
昨天
12
0
单元测试

右键方法 Go To --> Test,简便快速生成测试方法。 相关注解 @RunWith(SpringRunner.class) 表示要在测试环境中跑,底层实现是 jUnit测试工具。 @SpringBootTest 表示启动整个 Spring工程 @A...

imbiao
昨天
3
0
欧拉公式

欧拉公式表达式 欧拉公式的几何意 cosθ + j sinθ 是个复数,实数部分也就是实部为 cosθ ,虚数部分也就是虚部为 j sinθ ,对应复平面单位圆上的一个点。 根据欧拉公式和这个点可以用 复指...

sharelocked
昨天
5
0
burpsuite无法抓取https数据包

1.将浏览器和burpsuite的代理都设置好 2.在浏览器地址栏输入: http://burp 3.下载下面的证书,并将证书导入浏览器 cacert.der

Frost729
昨天
3
0
JeeSite4.x 消息管理、消息推送、消息提醒

实现统一的消息推送接口,包含PC消息、短信消息、邮件消息、微信消息等,无需让所有开发者了解消息是怎么发送出去的,只需了解消息发送接口即可。 所有推送消息均通过 MsgPushUtils 工具类发...

ThinkGem
昨天
9
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部