将双精度转换为整数时出错

Errors in Casting Doubles to Integers

本文关键字:出错 整数 双精度 转换      更新时间:2023-10-16

我正在计算超过小数点的有效数。我的程序丢弃小数点后间隔超过7个数量级的任何数字。我预计双打会有一些错误,所以我解释了从双打中减去int时弹出的非常小的数字,即使看起来应该等于零(据我所知,这是由于计算机如何存储和计算数字)。我的困惑是,给定这个随机测试值,为什么我的程序不能处理这个意外的数字。

在发表了许多声明后,当它试图投最后2票时,似乎会搞砸。无论何时强制转换,它都改为1。

bool flag = true;
long double test = 2029.00012;
int count = 0;
while(flag)
{
test = test - static_cast<int>(test);   
if(test <= 0.00001) 
{
flag = false;
}
test *= 10;
count++;
}

我发现的解决方案是一开始只投一次,因为四舍五入可能会产生负数并提前终止,然后再四舍五进。有趣的是,trunk和floor也有这个问题,似乎把应该是2的变成了1。

我和我的教授都很困惑,因为我完全预计会出现小数字(大多数在10^-10范围内),但没想到铸造、截断和地板也会失败。

重要的是要理解并非所有有理数都能以有限精度表示。此外,重要的是要理解,以十进制为单位以有限精度表示的一组数字与以二进制为单位以无限精度表示的数字不同。最后,重要的是要理解您的CPU可能以二进制形式表示浮点数字。

2029.00012恰好是一个在双精度IEEE 754浮点中无法表示的数字(它确实是一个双精度文字;您可能打算使用长双精度)。恰好可以表示的最接近的数字是2029.000119999999924402800388634204864501953125。所以,你计算的是这个数字的有效数字,而不是你使用的文字的数字。

如果0.00001的意图是在数字接近整数时停止计数数字,则不足以检查该值是否小于阈值,还不足以检查其是否大于1-阈值,因为表示误差可能会向任何方向发展:

if(test <= 0.00001 || test >= 1 - 0.00001)

毕竟,你可以多次将0.9999999999999999999999乘以10,直到结果接近零,即使这个数字非常接近一个整数。

正如许多人已经评论的那样,由于浮点数的限制,这将不起作用。当你说你预计双打会有"一些错误"时,你有一个正确的直觉,但这最终是不够的。在我的机器上运行您的特定程序,最接近2029.00012的可表示double是2029.0001199999999244(这实际上是一个截断值,但它很好地显示了9的序列)。因此,当你乘以10时,你会不断找到新的有效数字。

最终,问题是你在操纵一个以2为基数的实数,就像它是一个以10为基数的数字一样。这实际上相当困难。这方面最臭名昭著的用例是打印和解析浮点数,为此付出了大量的汗水和鲜血。例如,早在之前,您就可以欺骗官方Java实现无休止地循环,试图将String转换为double

你最好的办法可能就是重复使用那些艰苦的工作。打印到7位精度,并从结果中减去尾随零的数量:

#include <iostream>
#include <sstream>
#include <iomanip>
#include <string>
int main() {
long double d = 2029.00012;
auto double_string = (std::stringstream() << std::fixed << std::setprecision(7) << d).str();
auto first_decimal_index = double_string.find('.') + 1;
auto last_nonzero_index = double_string.find_last_not_of('0');
if (last_nonzero_index == std::string::npos) {
std::cout << "7 significant digitsn";
} else if (last_nonzero_index < first_decimal_index) {
std::cout << -(first_decimal_index - last_nonzero_index + 1) << " significant digitsn";
} else {
std::cout << (last_nonzero_index - first_decimal_index) << " significant digitsn";
}
}

感觉不尽如人意,但是:

  • 正确打印5
  • "令人满意"的替代方案可能更难实施

在我看来,你的第二个最佳选择是阅读浮点打印算法,并实现足够的浮点打印算法来获得要打印的值的长度,这并不完全是一项入门级任务。如果你决定走这条路,目前最先进的是Grisu2算法。Grisu2有一个显著的好处,它将始终打印最短的10进制字符串,该字符串将产生给定的浮点值,这似乎是您想要的。

如果你想要合理的结果,你不能只截断数字,因为有时浮点数字会比四舍五入的数字少一毛。如果你想侥幸解决这个问题,请将初始化更改为

long double test = 2029.00012L;

如果你想真正修复它,

bool flag = true;
long double test = 2029.00012;
int count = 0;
while (flag)
{
test = test - static_cast<int>(test + 0.000005);   
if (test <= 0.00001)
{
flag = false;
}
test *= 10;
count++;
}

我很抱歉破坏了你随意的订单;我不能遵守它们。根据我的一位计算机科学教授的说法,"理想情况下,计算机科学家永远不必担心底层硬件。"我想你的计算机科学教授可能也有类似的想法。