【发布时间】:2017-05-08 17:48:05
【问题描述】:
我编写了以下例程,它应该在第 n 位小数处截断 C++ 双精度。
double truncate(double number_val, int n)
{
double factor = 1;
double previous = std::trunc(number_val); // remove integer portion
number_val -= previous;
for (int i = 0; i < n; i++) {
number_val *= 10;
factor *= 10;
}
number_val = std::trunc(number_val);
number_val /= factor;
number_val += previous; // add back integer portion
return number_val;
}
通常,这很好用……但我发现对于一些数字,尤其是那些似乎在 double 中没有精确表示的数字,存在问题。
例如,如果输入是 2.0029,并且我想在内部将其截断到第五位,则双精度似乎存储为介于 2.0028999999999999996 和 2.00289999999999999999 之间的某个值,并且在小数点后第五位截断它会得到 2.00289,就数字的存储方式而言,这可能是正确的,但对于最终用户来说,这看起来像是错误的答案。
如果我在小数点后四舍五入而不是截断,一切都会好起来的,当然,如果我给出一个双精度表示,其十进制表示的小数点后有 n 位以上,它也可以正常工作,但是怎么做我修改了这个截断例程,以便由于 double 类型及其十进制表示不精确导致的不准确不会影响最终用户看到的结果?
我想我可能需要某种舍入/截断混合来完成这项工作,但我不确定我会如何编写它。
编辑:感谢到目前为止的回复,但也许我应该澄清一下,这个值不一定会产生输出,但这个截断操作可以是许多不同的用户指定的浮点数操作链的一部分。在多个操作中累积在双精度内的误差是可以的,但是任何单个操作(例如截断或舍入)都不应产生与其实际理想值相差超过一半的 epsilon 的结果,其中 epsilon 是表示的最小量级通过当前指数的双精度。我目前正在尝试消化下面 iinspectable 提供的关于浮点运算的链接,看看它是否能帮助我弄清楚如何做到这一点。
编辑:嗯,链接给了我一个想法,这有点骇人听闻,但它应该可以工作,即在我开始用它做任何其他事情之前,在函数的顶部放置一个像 number_val += std::numeric_limits<double>::epsilon() 这样的行。不过,不知道有没有更好的方法。
编辑:我今天在公共汽车上时有一个想法,我还没有机会彻底测试,但它的工作原理是将原始数字四舍五入到 16 位有效十进制数字,然后将其截断:
double truncate(double number_val, int n)
{
bool negative = false;
if (number_val == 0) {
return 0;
} else if (number_val < 0) {
number_val = -number_val;
negative = true;
}
int pre_digits = std::log10(number_val) + 1;
if (pre_digits < 17) {
int post_digits = 17 - pre_digits;
double factor = std::pow(10, post_digits);
number_val = std::round(number_val * factor) / factor;
factor = std::pow(10, n);
number_val = std::trunc(number_val * factor) / factor;
} else {
number_val = std::round(number_val);
}
if (negative) {
number_val = -number_val;
}
return number_val;
}
由于双精度浮点数无论如何只能有大约 16 位精度,这可能适用于所有实际目的,但最多只有一位精度,否则双精度可能支持。
我想进一步指出,这个问题与上面建议的重复问题不同,因为 a) 这是使用 C++,而不是 Java...我没有 DecimalFormatter 便利类,并且 b) 我想要截断,而不是四舍五入,给定数字的数字(在 double 数据类型允许的精度范围内),以及 c) 正如我之前所说,这个函数的结果是 not 应该是一个可打印的字符串......它应该是这个函数的最终用户可能选择进一步操作的本机浮点数。由于双精度类型的不精度导致的多次操作累积错误是可以接受的,但任何单个操作都应该在双精度数据类型的精度限制范围内正确执行。
【问题讨论】:
-
What Every Computer Scientist Should Know About Floating-Point Arithmetic。有了这个,不要试图修改你的浮点值。如果您需要截断值,请在面向用户的界面中执行(例如,在格式化显示值或序列化为文本时)。
-
您正在尝试的内容原则上是不可能的。请参阅here 了解原因以及迭代证明。
-
我几乎可以肯定这是重复的。将浮点值截断到指定数量的 小数 位没有多大意义;例如
1.23不能用二进制浮点数精确表示。这种截断唯一有意义的情况是当您从1.2345这样的浮点值生成人类可读的字符串(如"1.23")时。 -
不可能。以你自己的例子为例,假设计算机看到输入
2.0028999999999999996,它是2.0029的不精确表示还是2.0028999999999999996的精确表示,还是介于两者之间?计算机无法做到这一点。充其量您可以将浮点数截断为指定的 binary 数字。对于十进制数字,您不能这样做。 -
除了 hack 之外,添加 epsilon() 对大于或等于 2.0 的值没有任何作用。您正在尝试解决无法解决的问题。如果您需要准确存储小数,则必须使用能够做到这一点的表示。 Binary-coded decimals 是您尝试解决的问题的一种常见解决方案。
标签: c++ rounding truncation