将双精度转换为整数的错误

时间:2019-01-29 23:53:59

标签: c++

我正在计算小数点后的有效数字数。我的程序将舍弃小数点后间隔超过7个数量级的所有数字。预期双精度会出现一些错误,所以我认为从双精度中减去整数时会弹出一个很小的数字,即使看起来它应该等于零(据我所知,这是由于计算机如何存储和计算数字)。我的困惑是为什么在给定此随机测试值的情况下,我的程序不处理此意外数字。

已经放置了很多cout语句,当试图强制转换最后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++;

}

我发现的解决方案是在开始时只进行一次铸造,因为四舍五入可能会产生负数并过早终止,然后四舍五入。有趣的是,trunc和floor也都遇到了这个问题,似乎将应该从2变成1。

我和我的教授都感到很沮丧,因为我完全希望会出现少量数字(大多数都在10 ^ -10范围内),但并不期望铸造,修剪和铺地板也会失败。

3 个答案:

答案 0 :(得分:3)

重要的是要理解并非所有有理数都可以有限精度表示。同样,重要的是要理解以十进制为单位的有限精度可表示的数字集与以二进制为单位的有限精度可表示的数字集不同。最后,重要的是要明白,你的CPU可能代表二进制浮点数字。

2029.00012特别是在双精度IEEE 754浮点数中无法表示的数字(它的确是双精度字面量;您可能打算使用长双精度数)。碰巧可以代表的最接近的数字是2029.000119999999924402800388634204864501953125。所以,你计算这个数字的显著数字,您所使用的文字而不是数字。

如果0.00001的目的是在数字接近整数时停止对数字进行计数,则仅检查该值是否小于阈值还不够,还要检查其是否大于1 - 阈值,以表示误差可以去任何一种方式:

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

毕竟,即使该数字非常接近整数,您也可以将0.99999999999999999999999999乘以10多次,直到结果接近于零为止。

答案 1 :(得分:3)

由于已有多个人发表评论,因此由于limitations of floating-point numbers而行不通。当您说您希望“加倍”出现“某些错误”时,您的直觉有些正确,但这最终还不够。在我的计算机上运行您的特定程序,最接近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 digits\n";
    } else if (last_nonzero_index < first_decimal_index) {
        std::cout << -(first_decimal_index - last_nonzero_index + 1) << " significant digits\n";
    } else {
        std::cout << (last_nonzero_index - first_decimal_index) << " significant digits\n";
    }
}

感觉不令人满意,但是:

  • 它可以正确打印5;
  • “令人满意”的替代方案可能很难实施。

在我看来,您的第二好选择是阅读浮点打印算法,并实施足够多的算法以获取要打印的值的长度,但这不完全是介绍性的-级任务。如果您决定走这条路,那么当前的技术水平是Grisu2 algorithm。 Grisu2的显着优势是它将始终打印最短的以10为底的字符串,该字符串将产生给定的浮点值,这正是您所追求的。

答案 2 :(得分:1)

如果要获得合理的结果,则不能只截断数字,因为有时浮点数的长度要比四舍五入的长度短。如果您想通过fl幸解决此问题,请将您的初始化更改为

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++;
}

我很抱歉为您无意中缩成一字;我不能遵守它们。根据我的CS教授之一,“理想情况下,计算机科学家从未有对底层硬件的担心。”我想您的CS教授可能有类似的想法。