如何进行浮点数舍入并带有偏差(始终向上或向下舍入)?

7
我希望能够对浮点数进行偏差舍入,要么总是向下取整,要么总是向上取整。代码中有一个特定的点需要这样做,程序的其余部分应该像往常一样四舍五入到最近的值。
例如,我想要将数字四舍五入到最接近1/10的倍数。最接近7/10的浮点数大约为0.69999998807,但最接近8/10的数大约为0.80000001192。当我舍入数字时,这些是我得到的两个结果。我宁愿按同一方式舍入它们。7/10应该四舍五入为0.70000004768,而8/10应该四舍五入为0.80000001192。
在这个例子中,我总是向上舍入,但有一些地方我想总是向下舍入。幸运的是,在每个这样的地方,我只涉及正值。
我正在使用的舍入代码是floor(val * 100 + 0.5) / 100。我用C++编程。

顺便提一下,你可以从我删除的答案中得到的要点(我意识到它没有解决你问题的关键部分)是:通常将一个值四舍五入到任意精度“p”的方法是:floor(val / prec + 0.5) * prec。比你现在写的更直观。 - chaos
这种不直观的原因可能是您的表达式四舍五入到最近的1/100,而不是最近的1/10。 - chaos
为什么有人更改了这篇帖子的标签?没有添加任何标签,只是更改了顺序。这似乎是一个无聊的编辑,所以我已经将其恢复。 - user4891
1个回答

11

我认为实现这个最好的方法是依靠IEEE 754浮点标准,该标准规定浮点位的整数表示按字典顺序排列,就像2补码整数一样。

也就是说,您可以简单地添加一个ulp(最后一位单位)来获得下一个浮点表示(如果原始值更小,则新值始终比阈值略大,因为舍入误差最大为1/2 ulp)

例如:

 float floatValue = 7.f/10;
 std::cout << std::setprecision(20) << floatValue << std::endl;
 int asInt = *(int*)&floatValue;
 asInt += 1;
 floatValue = *(float*)&asInt;
 std::cout << floatValue << std::endl;

在我的系统上打印

 0.69999998807907104492
 0.70000004768371582031

为了知道何时需要添加一个ulp,你需要依靠 floor 和四舍五入的 floor 之间的差异。

 if (std::floor(floatValue * 100.) != std::floor(floatValue * 100. + 0.5)) {
    int asInt = *(int*)&floatValue;
    asInt += 1;
    floatValue = *(float*)&asInt;
 }

该函数可以正确将0.69..转换为0.70..但不会改变0.80..的值。

需要注意的是,在应用floor之前,浮点数通过与100.相乘来晋升为双精度浮点数。

如果没有这样做,你可能会面临以下情况:

 7.f/10.f * 100.f

浮点数(精度有限)的表示将为70.00...


1
+1 针对惊人的位操作技巧。读完后我感觉更聪明了。 - J. Polfer
很酷 :) 更简单的方法是直接使用别名:(int&)floatValue += 1; - Iraimbilanja
2
我刚刚发现了C99中的一个函数叫做nexttoward,可以用来递增ulp,很好地掩盖了位操作。 http://www.penguin-soft.com/penguin/man/3/nextafter.html - user4891

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接