※ 引述《lovejomi (JOMI)》之铭言:
: a. stackoverflow的作法错了?
: b. 为什么会把差值当成相等?
: c. 到底这个epsilon 最应该用在哪里呢?
: d. 是不是把almost_equal当成一个正解 才是正确的浮点数比较相等呢?
: 我用以下tool 把 epsilon 看他hex form 反推一下
: 他是2^-23 = 0.00000011920928955078125f乍看之下跟gcc定义一致
: https://www.h-schmidt.net/FloatConverter/IEEE754.html
: 观念上有些错误
: 请大家修正一下
: 谢谢
浮点数虽然可能因为实作不同而有差异性,但扣除一些特殊的值之后
浮点数型别能毫无误差表示的数值跟整数能表示的数值性质相似
都是落在数线上的离散格子点.
跟整数不同的地方在于,相邻的整数格子点间的距离是相等的
但是相邻的浮点数格子点间的距离可能不相等 (与指数的大小和 epsilon 有关: epsilon * base^exp)
整数: | | | | | | | | | |
(相邻的整数间都是等宽)
浮点数: | | | | | | | | | | | | | | |
(相邻的浮点数间距离,会随着浮点数的大小而可能有所不同)
要透过浮点数做运算时大概有三个步骤
1. 将要计算的数字转换成浮点数可精确表示的格子点 (可能产生误差)
2. 对这些在格子点上的数字们做运算
3. 将算出来的结果存在浮点数可精确表示的格子点上 (可能产生误差)
以对 x + y 做计算为例.
因为 x 或 y 可能不是目前所用浮点数实作可以精确表达的数字
(例如 0.1 在常见的浮点数实作上无法准确落在格子点上)
所以首先要将 x 与 y 转换到各自最近的浮点数格子点上,此时就产生误差
(例如 0.1 + 0.1 时会先将两个 0.1 都转换到最近的浮点数格子点,造成运算完的结果可能不完全等于 0.2)
假设将 x 转换到 x', y 转换到 y',则令
x = x' + dx (dx 表示 x 转换到格子点 x' 产生的误差)
y = y' + dy (dy 表示 y 转换到格子点 y' 产生的误差)
因此当我们运算 x + y 时,实质上是用 x' + y' 去运算
同时运算的结果,也可能不落在格子点上
假设我们将 x' + y' 运算的结果取名为 z,则将 z 转换到最近的格子点 z' 的话
z = z' + dz (dz 表示 z 转换到格子点 z' 产生的误差)
综合以上所说,
原本我们想要算 x + y ,也就是 (x' + dx) + (y' + dy) = x' + y' + dx + dy (例如 0.1 + 0.1)
最后却变成是算出 z', 也就是 z - dz = x' + y' - dz (例如 0.1 + 0.1 的结果)
最后原本想算的和与真正算出的结果间的误差就变成是 |x + y - z'| = |dx + dy + dz|
如果 |dx + dy + dz| == 0 的话,去比较 x + y == z' 通常是没问题
但是不为 0 的时候,dx + dy + dz 和的大小范围就会影响我们判断是否可能相等时的条件范围
而我们怎么知道 dx + dy + dz 的大小范围呢 ?
这和当时 x, y, z 的大小有关
因为误差大小通常不会大于浮点数格子点间一半的距离
而浮点数格子点间距离会随指数与 epsilon 不同而不同
epsilon 跟浮点数实作有关,而指数大小则与目前的值大小有关
这边 epsilon 表示的是 1 跟下一个比 1 大的浮点数格子点的距离
可以想成不考虑 base^exp 的大小的情况下 (exp = 0),两个浮点数格子点间最近的距离
(当然这里我们去掉了一些特殊的值,例如 subnormal number, +-0)
但是因为 base^exp 会放大格子间距离,所以格子间距离要多乘上 base^exp 而与 epsilon * base^exp 有关
因此考虑 dx + dy + dz 这误差可能的范围时,要考虑 dx, dy, dz 的大小
而 dx, dy, dz 的大小与 x, y, z 当时的值有关 (影响 base^exp 的大小)
此外还要考虑 epsilon 跟允许的误差计算次数等
当然我知道我这边举 0.1 当例子可能不好 (通常是 subnormal number),只是为了方便理解
草草写写,有错误疏漏也请不吝指教
※ 编辑: Feis (140.122.83.198), 09/19/2018 00:47:44