Julia语言程序设计
上QQ阅读APP看书,第一时间看更新

3.3.3 epsilon

实际上,由于位数所限,并不是每个实数都能在计算机中有准确的表达,计算机中的浮点型数值也只是实数集很小的子集之一。所以,计算机中相邻两个浮点型值之间存在着“缝隙”,是其无法表达的实数区域,也影响着科学计算的精度。为了能够评估这种误差,相邻浮点值间这种缝隙的宽度被称为epsilon值。

Julia中可通过eps()获得某个浮点值附近的epsilon值,下面以Float64类型的浮点数为例:


julia> eps(100000.0)
1.4551915228366852e-11

julia> eps(1000.0)
1.1368683772161603e-13

julia> eps(100.0)
1.4210854715202004e-14

julia> eps(1.0)
2.220446049250313e-16

julia> eps(-1.0)
2.220446049250313e-16

julia> eps(1e-27)
1.793662034335766e-43

julia> eps(0.0)
5.0e-324

细心的读者能够发现,浮点值不同时epsilon值也不同。

实际上,随着浮点值(绝对值)从大到小,epsilon值会呈指数级骤减,到零值处最小。而且epsilon值的大小关于零点对称,与浮点值的正负无关。换言之,在计算机能够表达的浮点数集合中,越靠近零点,数值的分布越稠密;而远离零点时,则会变得越来越稀疏,精度也会越来越差。

也可以直接取得某个浮点类型的epsilon值,代码如下:


julia> eps(Float16)        # 等于eps(one(Float16))
Float16(0.000977)

julia> eps(Float32)        # 等于eps(one(Float32))
1.1920929f-7

julia> eps(Float64)        # 等于eps(one(Float64))
2.220446049250313e-16

julia> eps()               # 等于eps(one(Float64))
2.220446049250313e-16

不过以类型获得epsilon值时,上报的“缝隙”值实际是浮点值为1.0处的情况。若调用该函数未提供参数,则取默认浮点类型在值为1.0处的epsilon值,例如在64位系统中,esp()相当于eps(one(Float64))。

正是由于epsilon值的存在,计算机中的浮点数并不是无限稠密的,而是一系列离散的数值。而且离散的浮点数是有序的,所以可以进行逐一遍历。

内置的nextfloat()与prefloat()函数可以分别获得某个浮点值的后继与前继浮点值。例如:


julia> x = 1.25f0
1.25f0

julia> nextfloat(x)
1.2500001f0

julia> prevfloat(x)
1.2499999f0

如果我们查看x及其前继、后继的在二进制表达,便能够发现它们之间的大小只差一个进制位,即:


julia> bitstring(prevfloat(x))
"00111111100111111111111111111111"

julia> bitstring(x)
"00111111101000000000000000000000"

julia> bitstring(nextfloat(x))
"00111111101000000000000000000001"

这实际也是浮点表达中epsilon值存在的重要原因。

当然,也可以通过x+eps(x)或x-eps(x)分别获得x的后继与前继值,代码如下:


julia> x + eps(x)                                         # 等于nextfloat(x)
1.2500001f0

julia> x - eps(x)                                          # 等于nextfloat(x)
1.2499999f0

这样的遍历操作在一些需要高精度数值迭代的场景会比较有用。