[译文]5分钟系列—快速理解Floating-point Numbers(浮点数)

原文链接:What Are Floating-point Numbers?

前言

浮点数是一种以二进制形式存储数字的方法, 它允许我们使用固定数量的存储空间来表示”大范围的值”

为什么要了解浮点数?

了解了浮点数,以下两个问题你就能马上明白

  • 为什么 0.1 + 0.2 在某些情况下并不等于 0.3
  • 如何用二进制的形式存储小数( non-integer)

5分钟带你了解浮点数

首先,我们来看看二进制是怎么存储整数的。从低到高(右到左)每一位都表示 2 的幂 , 通过按权展开求和,我们能得到想要的整数,示例如下:

二进制表示整型

二进制表示整数,完美。但是怎么用二进制表示小数呢?比如:2.5

聪明的你肯定想到了,把二进制位分为两部分。左边部分表示小数点前面的数字(示例中的2),右边的部分表示小数点后面的数字(示例中的0.5),于是上图变为如下:

二进制表示非整型

看似解决,但仍然还是有很多小数没法表示,比如: 2.36 就没法表示,最接近的小数是2.375(用计算机术语就是:缺失精度),甚至我们都无法用它表示16.0( 因为左边只有四位,最大值只能表示15)

我们确实可以通过扩展位数用来增加所表示的小数的“大小”和“精度”,但是这样做不够灵活。有时候我们想存储值很大的小数,我们就希望左侧有更多位数;有时候我们想存储尾数(小数点后的数字)很大的小数,就肯定希望右边有更多的位数。那有没有一种方法,能动态满足我们的需求呢?它就是浮点数

浮点数的国际标准是:IEEE 754 ,它定义了32位和64位浮点值;我们用32位浮点值来举例,它的二进制结构如下(从左到右):

  • 第一位代表”符号(Sign)”; 0 表示整数,1表示负数
  • 接下来的8位代表”指数(Exponent)”
  • 最后的23位代表”尾数(Mantissa)”

我们在一个公式中,用到这三个值,公式计算的结果就是实际表示的数字:

你不用理解这个公式的具体运作方式,只要知道这种方式可以让我们更灵活更自由的用二进制表达小数。这也是为什么称之为:”浮点”, 它不像我们上面的示例,小数点在中间,相反它能通过调节指数从而移动小数点的位置,是不是很神奇?

舍入误差

有些分数,其实我们也无法用小数来准确表示。比如: ⅓ = 0.33333333333333…

同理,有些小数二进制也没法精确表示。比如:让我们尝试用二进制表示 0.1,大概如下:

我们可以通过减小指数让它无限接近0.1,但始终没法精确到刚好是0.1。这也是为什么你用chrome浏览器(按F12),在Console 输入: 0.1+0.2 回车,得出的结果是: 0.30000000000000004

因此但是考虑到可能会有这种结果,我们在比较浮点数是否相等的时候,方式2 比较合理:

float result1, result2;
#define RoundingValue = 0.0001f
//方式1:这种写法非常不推荐
if(result1 == result2)
{
    //do some thing 
}

//方式2:推荐写法
if(Math.abs(result1 - result2) < RoundingValue )
{
    //do some thing
}

拓展链接: