Re: [问题] float 精准度观念问题

楼主: LPH66 (-6.2598534e+18f)   2018-09-19 08:04:51
有另外一个观念其实对于理解浮点数运算也很有用:
浮点数其实就是有限有效位数的二进制科学记号
既然是科学记号那一些关于其运算的观念换个底就能套用在浮点数身上
※ 引述《lovejomi (JOMI)》之铭言:
: 我理解为什么float会有误差值
: 但是今天朋友讨论一件事情
: if (float_var == 1.0f) 这样写到底有什么错(我认知是 这样写 变量的值要完全跟1.0
: 四个byte的memcmp要一样)
: 1. 在误差范围内 (https://en.cppreference.com/w/cpp/types/numeric_limits/epsilo
: n)
: 如果是趋近于1的数字 我这样判断会失败 导致逻辑错误? 所以因为这样条件太严苛
: 对于经过运算后的float数值 很可能有一点点误差产生就不成立了?
: 2. 如果是要完全的相等 , 我能把一个float 一个byte一个byte判断是否相等来判断是不
: 是等值吗?
: 例如
: typedef union
: {
: float value;
: unsigned char bytes[4];
: } IEEE754;
: IEEE754 one;
: one.value = 1.0f;
: IEEE754 target;
: target.value = input;
: 然后memcmp 两者的bytes
: 还是 float 的== 实作上就是byte compare?
: 3. 浮点数运算出现误差,可以理解成 当除不尽 或是 除完小数点超过二进制小数 23位
: 无法表示
: 就会产生误差?
: 4. 因为看不懂std::numeric_limits<T>::epsilon 的那个almost_equal在干嘛 所以找了
: 一下
: https://stackoverflow.com/a/17341/588477
: 这篇的方法好像是有道理但是请看以下测试
: https://ideone.com/MH6jJW
: 我看VC直接写
: #define FLT_EPSILON 1.192092896e-07F // smallest such that 1.0+FLT
: _EPSILON != 1.0
: GCC我用gcc -E -dM 去dump (我不知道为什么找不到定义???怎么解释 https://tinyurl.
: com/y8heekq8 )
: #define __FLT_EPSILON__ 1.19209289550781250000e-7F
: 奇怪为什么会是这样
: 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
: 观念上有些错误
: 请大家修正一下
: 谢谢
我们知道在用有限位数的科学记号表示实际值时会进行四舍五入
所以当使用这些经过处理的值运算就有可能产生误差
十进制科学记号的对应例子例如: 1/3 可能会表示成 3.33333*10^-1
所以当三个这个值相加时会得到的是 9.99999*10^-1, 而不是 1.00000*10^0
为了这个原因我们通常会取一个小范围, 两者相减在误差范围内的话就视作相等
例如若想要差在 1*10^-4 以内当作相等的话, 上面两个结果就会当作相等了
到这里回答了你的 (1) 和 (3)
(2) 其实所有数值型态的 == 都是 byte exact compate
所以才会说直接用 == 比的结果可能不是你要的
(4) 这里则是又一个科学记号的名词出现了: ulp
ulp 全名 units in the last place, 直译叫做“最后一位的单位”
其实指的就是这个科学记号的表示法当中最后一个有效位数上有 1 的数值
以上面我举例的十进制六位有效数字来说
1.00000*10^0 的最后一个有效位数其值为 1*10^-5, 这就是这个浮点数的 ulp
这个单位通常用来表示误差大小, 例如上面 1/3 + 1/3 + 1/3 的计算误差就是 1 ulp
会拿这个当误差大小的理由看科学记号的表示法很容易理解
那这里的 epsilon 值其实就是代表某种型态的 1 的 ulp 大小
(这个 epsilon 的定义有个讲法是最小的值使其加上 1 之后不等于 1
仔细想想这其实就是在说这个 epsilon 是表示 1 这个浮点数的 ulp 大小)
因此 cppreference 里的范例就是在说
“x-y 的值 (两者的误差) 要小于给定参数个 ulp”
之所以要乘以 x+y 是为了要求得所求数值范围里的 ulp 实际值
因为很容易理解 1.00000*10^0 和 4.20000*10^1 和 5.77216*10^-1 的 ulp 都不同
实际上我们需要的 ulp 是多少可以用该值乘上 epsilon 来估计
(参照上一篇回答, 这个 ulp 间隔其实就是上一篇里提的那些离散格子点)
那么回到你的问题, 是不是一定要比较浮点数都要用它的 almost_equal 实作?
答案其实是要看你的用途而定
如果你的用途会需要这种类型的误差估计, 那就需要
它会保证你的数字之间的误差不超过某个数学上可以掌握的大小
如果只是平常的计算的话, 那其实可以不用这么严格
像我最上面那样抓一个大致足够的小值, 相差不到这里就当它相等就好
这个足够的小值可以参照这些 epsilon 的实际值来订
例如 std::numeric_limits<float>::epsilon (FLT_EPSILON)
其值是 2^-23 约是 1.192093 * 10^-7
所以对于 float 可以简单取一个 10^-5 (写 1e-5f) 当做平常在用的误差值
取 100 倍左右的范围可以适用到稍微大范围一点的数字
而对于现在比较常用的 double
std::numeric_limits<double>::epsilon (DBL_EPSILON)
其值是 2^-52 约是 2.220446*10^-16, 所以很常看到取 1e-10 ~ 1e-13 来用的写法
作者: cutekid (可爱小孩子)   2018-09-19 09:55:00
大推(Y)
作者: lovejomi (JOMI)   2018-09-25 17:18:00
谢谢讲解, 有更多领悟 不过还是需要思考一下

Links booklink

Contact Us: admin [ a t ] ucptt.com