C++ 舍入算法中的 0.501,C++ 中的 Excel ROUND
Posted
技术标签:
【中文标题】C++ 舍入算法中的 0.501,C++ 中的 Excel ROUND【英文标题】:0.501 in the C++ rounding algorithm, Excel ROUND in C++ 【发布时间】:2016-02-22 14:40:32 【问题描述】:我有以下用于通常舍入的旧代码(四舍五入,类似于 Excel ROUND 函数中的舍入)。我对此代码有疑问。
-
为什么要使用 0.50x 加法?我知道,应该使用 0.5 加法来实现“四舍五入”条件,但为什么会有 0.501?我们的经验表明,0.501 在实践中效果很好,它引入的舍入误差(RoundFloat 与 Excel ROUND 不同的情况)比 0.5 少得多。这是为什么?浮点计算是否偏向于给出比理想情况下更小的值,而 0.501 可以纠正这些偏差?
虽然 0.501 在大多数情况下效果很好,但某些值存在问题,例如RoundFloat(7.33499999999, 2) 给出 7.34,但这是错误的,正确答案应该是 7.33。如何改进算法以获得正确的结果?可以尝试使用 0.50000001 而不是 0.501,但总有一些参数会给出错误的结果。 什么是正确的算法,是否有内置的 C++ 函数用于 Excel 风格的 ROUND 舍入。
提前致谢!
double LongLong( double value )
long long l = ( long long ) value;
return l;
double RoundFloat( double * value, double * tonearest )
double ad;
long long mzr;
double resval;
if ( ( *tonearest < 0 ) || ( *tonearest > 6 ) ) return * value;
if ( *value < 0.000000001 )
ad = -0.501;
else
ad = 0.501;
mzr = LongLong(*value);
resval = *value - mzr;
switch ( ( long ) *tonearest )
case 0 : resval= LongLong( resval+ad);
case 1 : resval= LongLong( resval*10+ad)/10;
case 2 : resval= LongLong( resval*100+ad)/100;
case 3 : resval= LongLong( resval*1000+ad)/1000;
case 4 : resval= LongLong( resval*10000+ad)/10000;
case 5 : resval= LongLong( resval*100000+ad)/100000;
case 6 : resval= LongLong( resval*1000000+ad)/1000000;
default: resval= resval;
resval = resval+mzr;
return resval;
【问题讨论】:
【参考方案1】:(我将此答案限制为 IEEE754 浮点,这是 Excel 使用的。)
Excel 使用0.5(舍入 pivot),因为这是正确的做法。请注意,0.5(作为二元有理数)可以用浮点数精确表示。
使用 0.501 是任意的、过度不对称的,并且表明缺乏对浮点的理解。
您可以得到一些虚假效果,因为数字略小于 0.5,而 0.5 作为最接近的可表示数字。所以你的“少一点”数字会被四舍五入,即使它真的应该被四舍五入。但这更多是因为数字一开始就无法准确表示。因此,虽然 0.5 会给您带来一些奇怪的效果,但它平均而言会比 0.501 或类似值更准确。并且执行诸如添加超过 0.5 之类的操作会混淆向下舍入最佳的情况。
虽然我专注于 0.5,但所有舍入效果都可以用相同的方式解释,尽管在其他情况下舍入枢轴本身可能无法表示。
学会接受效果,就像 Excel 一样。 (请记住,Excel 可以以一种对用户隐藏这些影响的方式来格式化数据,因此它似乎在做一些比实际更特别的事情。)如果计算准确到超过 15 个显着性是绝对关键的数字,然后使用支持任意精度的类。
【讨论】:
以上是关于C++ 舍入算法中的 0.501,C++ 中的 Excel ROUND的主要内容,如果未能解决你的问题,请参考以下文章
C++ 中的舍入值。为啥 printf、iostream 和 round 函数 [可能] 表现不同,具体取决于 Visual Studio 和 Windows 版本?