C ++中字符串转换为双精度浮点数的奇怪问题

3

我需要从文本文件中读取一些数据(以double精度)。但有时,使用atofstring转换为double会得到奇怪的结果。以下是一个代码片段,它演示了这个问题-请注意,我使用的是GNU C++ 4.8.1编译器。该代码简单地打印出介于-0.1和0之间、步长为0.01的数字;因此只期望得到9个数字。

    #include <iostream>
    #include <cstdlib>

    int main() {
    int y;
    double a, b = 0.0, x = 1e-2, z;

    double a_string = atof("-0.1");
    double a_inline = -0.1;

    std::cout << "Inline:: ";
    a = a_inline;
    for (y = 1 ; (z = a + y * x) < b; ++y){
    std::cout << y << ": " << z << " | ";
    }

    std::cout << "\nString:: ";
    a = a_string;
    for (y = 1 ; (z = a + y * x) < b; ++y){
    std::cout << y << ": " << z << " | ";
    }

    return 0;
    }

结果是

    Inline:: 1: -0.09 | 2: -0.08 | 3: -0.07 | 4: -0.06 | 5: -0.05 | 6: -0.04 | 7: -0.03 | 8: -0.02 | 9: -0.01 | 
    String:: 1: -0.09 | 2: -0.08 | 3: -0.07 | 4: -0.06 | 5: -0.05 | 6: -0.04 | 7: -0.03 | 8: -0.02 | 9: -0.01 | 10: -3.46945e-18 | 

请注意,基于字符串赋值(a_string)的for循环比基于内联赋值(a_inline)的循环运行多一次 - 注意最后一个数字-3.46945e-18。尽管我已经阅读了其他相关帖子关于字符串转double的内容,但仍然无法弄清楚为什么会出现这种情况。

1
注意:在我的设备上(基于LLVM 3.4svn的Apple LLVM版本5.1(clang-503.0.40),编译为x64),它按预期运行(每个循环9个值)。有趣的是,Coliruideone.com产生不同的结果。 - WhozCraig
它对我也很有效,GNU C++ 4.3.2,x64。 - blackmesa
在我看来,atof(“-0.1”)必须作为字符串解析器工作,因此它不能仅从“-0.1”字符串中获取-0.1值,它通过对某些值行求和并将其乘以-1.0(在您的情况下为-1.0 * 0.1)来获得最终值。我认为这个乘法是原因。在此之后,您的字符串值将变成类似于-0.100000000000001的东西。 - Arsenii Fomin
1
@Fomin Arseniy 循环中没有明显的累积。 - Daniel Heilper
我并不是在说累积误差,而是关于a_string的起始值出现了错误。 - Arsenii Fomin
显示剩余2条评论
2个回答

4

二进制浮点数通常不是十进制分数的精确表示。直接比较不同计算结果或计算结果与文字量之间的差异,将会导致出乎意料的错误。在比较时必须使用区间。这个问题与atof本身关系不大。


2

您的代码相当于...

std::atof("-0.1") - 10 * 1e-2

...不等于精确的0。这就是使用实数表示法所面临的问题,它们只能编码其范围内分布的有限数量的点的近似值,并且一些生成/处理它们的函数甚至会对那些恰好可表示的数字的最后一位或两位引入误差。在您的情况下,-0.1无法被完美地表示。为了更好地理解这一点,将“-0.1”输入到此在线IEEE 754转换器的“十进制表示”文本框中,您会看到32位float中可能的二进制表示仅是近似值...在64位double中也同样如此...只是近似得更好。

通常的建议是阅读《计算机科学家应该知道的浮点值的每件事》

在不同时间或以不同方式计算每个x时,数学上的“x-x”可能不为0,而且它的具体结果可能因优化级别、周围代码、硬件、编译器等而有所不同...设计能够优雅地处理这些问题的代码有点像艺术。在您的情况下,如果您想要循环9次,则计数为9,如果您想要某种比较的容差,则编写允许在比较中有一定误差(一个“epsilon”值)的内容。您还可以使用将浮点数值一位一位地提升到下一个可表示值的函数,这样可以避免问题,在特定指数中缩放epsilon值,使其对应于最后一位或两位的有效数字,但是这也是一种游戏,因为难以知道在生成值时累积了多少表示和舍入误差。


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