atof和stringstream产生不同结果

5

我一直在研究一个问题,即如何将浮点数转换为人类可读格式,再转回来。也就是字符串。我使用stringstream遇到了问题,发现atof可以产生“更好”的结果。

注意,这种情况下我没有打印出数据,我使用调试器检索值:

    const char *val = "73.31";
    std::stringstream ss;
    ss << val << '\0';
    float floatVal = 0.0f;
    ss >> floatVal; //VALUE IS 73.3100052

    floatVal = atof(val); //VALUE IS 73.3099976

这可能有一个合理的解释。如果有人能够为我解惑,我将不胜感激 :)。

1
<< '\0' 是多余的。 - emlai
2
@n.m. 可以在 MSV2015 中重现:73.310005187988373.3099975585938 - Simon Kraemer
你使用什么平台和编译器? - schorsch_76
2
如果你从 double 类型转换为 float 类型,你就会失去精度。这就是问题所在。 - schorsch_76
显示剩余10条评论
1个回答

2

回答基于 OP 使用 MSVC 的假设

atof 在读取浮点数方面确实比 istream 更好。

以下是一个例子:

#include <iostream>
#include <sstream>
#include <iomanip>
#include <cstdlib>

int main()
{
    const char *val = "73.31";
    std::stringstream ss;
    ss << val;
    float floatVal = 0.0f;
    ss >> floatVal;
    std::cout << "istream>>(float&)                       :" << std::setw(18) << std::setprecision(15) << floatVal << std::endl;

    double doubleVal = atof(val);
    std::cout << "double atof(const char*)                :" << std::setw(18) << std::setprecision(15) << doubleVal << std::endl;

    floatVal = doubleVal;
    std::cout << "(float)double atof(const char*)         :" << std::setw(18) << std::setprecision(15) << floatVal << std::endl;

    doubleVal = floatVal;
    std::cout << "(double)(float)double atof(const char*) :" << std::setw(18) << std::setprecision(15) << floatVal << std::endl;
}

输出:

istream>>(float&)                       :  73.3100051879883
double atof(const char*)                :             73.31
(float)double atof(const char*)         :  73.3099975585938
(double)(float)double atof(const char*) :  73.3099975585938

编译器甚至会警告这种从 double 转换为 float 的情况:
warning C4244: '=': conversion from 'double' to 'float', possible loss of data

我还找到了这个页面:浮点类型的转换


更新:

数值 73.3099975585938 似乎是正确的 float 解释 double 数值 73.31


更新:istream>>(double&) 同样可以正常工作:

#include <iostream>
#include <sstream>
#include <iomanip>
#include <cstdlib>

int main()
{
    const char *val = "73.31";
    std::stringstream ss;
    ss << val;
    double doubleVal = 0.0f;
    ss >> doubleVal;
    std::cout << "istream>>(double&) :" << std::setw(18) << std::setprecision(15) << doubleVal << std::endl;
}

输出:

istream>>(double&) :             73.31

对于算术类型,istream::operator>> 使用 num_get::get。对于 floatnum_get::get 应该使用类似于 scanf("%g") 的东西。(来源)

但是:

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
#include <iomanip>
#include <cstdlib>


int main()
{
    std::string s = "73.31";
    float f = 0.f;
    sscanf(s.c_str(), "%g", &f);
    std::cout << std::setw(18) << std::setprecision(15) << f << std::endl;
}

输出:

73.3099975585938

对我来说,这似乎是Microsoft的num_get存在漏洞。


我在gcc、clang和vc上测试了这段代码。我唯一能看到的解释是 istream>>float 的实现方式。看起来VC做得对(使用float),而其他所有编译器都使用double,然后将结果转换为float。 - Alexander
@Alexander,不清楚你为什么认为VC实现是“正确”的。它没有将十进制数转换为最接近的二进制表示,而其他所有实现都这样做了。 - n. m.
那么你对流>>浮点数有什么期望?我们有几个选项:1. 先读入double,然后转换为float。2. 先读入long double,然后转换为float。3. 直接读入float。哪种方法是正确的? - Alexander
“它不会将十进制数转换为最接近的二进制表示,而其他所有方法都会这样做。” “是这样吗?当您查看测试时,您会发现gcc评估为'73.3099975585938'。因此,在'atof'之后进行的强制转换是正确的。” - Simon Kraemer
是的,atof后面的转换是正确的,但你把它称为“罪犯”,好像它有什么过错一样。 - n. m.
显示剩余3条评论

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