返回介绍

浮点数为什么不精确?

发布于 2025-01-22 00:38:51 字数 2526 浏览 0 评论 0 收藏 0

1 浮点数为什么不精确? 其实这句话本身就不精确, 相对精确一点的说法是: 我们码农在程序里写的 10 进制小数,计算机内部无法用二进制的小数来精确的表达。

什么是二进制的小数? 就是形如 101.11 数字,注意,这是二进制的,数字只能是 0 和 1。

101.11 就等于 1 * 2^2 +0 *2^1 + 1*2^0 + 1*2^-1 + 1*2^-2 = 4+0+1+1/2+1/4 = 5.75

下面的图展示了一个二进制小数的表达形式。 (注: 此图来自于《深入理解计算机系统》第 2 章)

从图中可以看到,对于二进制小数,小数点右边能表达的值是 1/2, 1/4, 1/8, 1/16, 1/32, 1/64, 1/128 ... 1/(2^n)

现在问题来了, 计算机只能用这些个 1/(2^n) 之和来表达十进制的小数。

我们来试一试如何表达十进制的 0.2 吧。

0.01 = 1/4 = 0.25 ,太大

0.001 =1/8 = 0.125 , 又太小

0.0011 = 1/8 + 1/16 = 0.1875 , 逼近 0.2 了

0.00111 = 1/8 + 1/16 + 1/32 = 0.21875 , 又大了

0.001101 = 1/8+ 1/16 + 1/64 = 0.203125 还是大

0.0011001 = 1/8 + 1/16 + 1/128 = 0.1953125 这结果不错

0.00110011 = 1/8+1/16+1/128+1/256 = 0.19921875 已经很逼近了, 就这样吧。

这就是我说的用二进制小数没法精确表达 10 进制小数的含义。 2 浮点数的计算机表示

那计算机内部具体是怎么表示的呢?

计算机不可能提供无限的空间让程序去存储这些二进制小数。

它需要规定长度, 在 Java 中, 提供了两种方式: float 和 double , 分别是 32 位64 位

可以这样查看一下一个 float 的内部表示(以 0.09f 为例): Float.floatToRawIntBits(0.09f)

你将会得到:1035489772, 这是 10 进制的, 转化成二进制, 在前面加几个 0 补足 32 位就是:

0 01111011 01110000101000111101100

你可以看到它分成了 3 段: 第一段代表了符号(s) : 0 正数, 1 负数 , 其实更准确的表达是 (-1) ^0

第二段是阶码(e):01111011  ,对应的 10 进制是 123

第三段是尾数(M)

你看到了尾数和阶码,就会明白这其实是所谓的科学计数法: (-1)^s * M * 2^e

对于 0.09f 的例子,就是: 0101110000101000111101100 * (2^123) 好像不对,这肯定远远大于 0.09f !

这是因为浮点数遵循的是 IEEE754 表示法, 我们刚才的 s(符号) 是对的,但是 e(阶码) 和 M(尾数)需要变换:

对于阶码 e , 一共有 8 位, 这是个有符号数, 特别是按照 IEEE754 规范, 如果不是 0 或者 255, 那就需要减去一个叫偏置量的值,对于 float 是 127

所以 E = e - 127 = 123-127 = -4

对于尾数 M ,如果阶码不是 0 或者 255, 他其实隐藏了一个小数点左边的一个 1 (节省空间,充分压榨每一个 bit 啊)。 即 M = 1.01110000101000111101100

现在写出来就是: 1.01110000101000111101100 * 2^-4 =0.000101110000101000111101100 = 1/16 + 1/64 + 1/128+ 1/256 + .... = 0.0900000035762786865234375

你看这就是 0.09 的内部表示, 很明显他比 0.09 更大一些, 是不精确的!

64 位的双精度浮点数 double 是也是类似的, 只是尾数和阶码更长, 能表达的范围更大。 符号位 :1 位 阶码 : 11 位 尾数: 52 位 (注: 这个图也是来源于《深入理解计算机系统》第二章) 上面的例子 0.09f 其实是所谓的规格化的浮点数, 还有非规格化的浮点数,这里就不展开了。 3 使用浮点数 由于浮点数表示的这种“不精确性”或者说是“近似性”, 对于精确度要求不高的运算还行, 如果我们用 float 或者 double 来做哪些要求精确的运算(例如银行)时就要小心了,很可能得不到你想要的结果。

具体的改进方法推荐大家看看《Effective Java》在第 48 条所推荐的“使用 BigDecimal 来做精确运算”。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文