Eri*_*hil 5
在 C++ 中将浮点数转换为定点时如何减少浮点舍入误差?
重新安排定点编码的计算以将结果四舍五入为整数,以便其中的所有算术都精确执行,直到舍入之前的单个除法,如mybits = bitset<16>(std::round((x*10 + i)*32/10));
. 这将产生正确的结果,直到超出i
= 317,169。(x += 0.1;
从循环中删除;x
在这个新公式中用作不变的值。)
问题源于 .1 无法以基于二进制的浮点格式表示,因此源文本0.1
被转换为 0.1000000000000000055511151231257827021181583404541015625(当 IEEE-754 “双精度”double
用于x
(in x += 0.1;
) 执行将理想实数和舍入为 中可表示的最接近值的运算double
,并且由于x
是float
,再次将其舍入为可表示的最接近值float
(通常为 IEEE-754“单精度”格式)。
迭代i中定点数的期望值为1051 + i /10,转换为具有五个小数位的定点编码。其编码为 (1051 + i /10) • 32 四舍五入到最接近的整数。所以我们要计算的值是round((1051 + i /10) • 32),其中“round”是所需的舍入到整数函数(例如舍入到最近的偶数,或者圆到最近的关系到远离)。
我们可以将其写为 ((1051•10 + i )•32) / 10 的分数。这样做的好处是 (1051•10 + i )•32 是一个整数,可以用整数或整数精确计算浮点算术,只要它保持在精确算术的范围内。(对于“单精度”格式,这意味着 (1051•10 + i )•32 ? 2 24,因此i ? 2 19 ?10,510 = 513,778。)
那么唯一不需要的舍入是在除法中。该除法发生在所需的舍入为整数之前,因此不会因任何其他操作而加剧。所以我们可以计算定点编码,std::round((x*10 + i)*32/10)
并且只关心除以十的舍入误差。(要使用std::round
,包括<cmath>
。请注意,std::round
从零开始舍入一半的情况。要使用当前的浮点舍入模式,通常默认舍入到最近的关系,使用std::nearbyint
。)
仅当除法中的舍入导致小数部分不完全是 ½ 的值 ( x •10 + i )*32/10 变为小数部分正好是 ½ 的值时,才会导致最终结果出现错误。(相反,不会发生导致 ½ 小数的值变成具有其他小数的值的情况,因为具有 ½ 小数的值可以用二进制浮点数精确表示,因此不会发生舍入。一个例外是如果这个数字太大了,以至于超出了可以表示任何分数的点。但是,这不会发生在 IEEE-754“单精度”格式中,除非该值也溢出 Q10.5 格式。)
假设正在使用四舍五入,任何计算结果最多为实数结果的 ½ ULP。(“ULP”代表“Unit of Least Precision”,即在给定指数缩放的情况下有效数字中最低位的有效位置值。)因此, ( x •10 + i )*32/10 可以四舍五入为带有小数的值½ 仅当其小数部分与该值相差最多 ½ ULP 时。任何此类商的最接近的分数部分可以是 ½ 而不是 ½ 是 4/10 或 6/10。这些与 ½ 的距离是 1/10。因此,只要 1/10 超过 ½ ULP,就会std::round((x*10 + i)*32/10)
产生所需的结果。
对于 [2 19 , 2 20 ) 中的数字,“单精度”格式的 ULP 为 2 ?4 = 1/16,小于 1/10。因此,只考虑非负数i
,只要(x*10 + i)*32/10
< 2 20,结果是正确的。对于x
= 1051,这给了我们 (1051•10 + i
)•32/10 < 2 20 ? i
< 317,170。
因此,我们至少可以使用mybits = bitset<16>(std::round((x*10 + i)*32/10));
until = 317,169。i
更多推荐
浮点,误差,转换为,中将,浮点数
发布评论