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
这样的遍历操作在一些需要高精度数值迭代的场景会比较有用。