C++中的scanf %la返回0

8
Win7 64-bit
gcc 4.8.2
g++ -Wall

我试图按照为什么scanf()需要"%lf"读取双精度浮点数,而printf()只需要"%f"就可以?中的定义来格式化我的C++ sscanf,但返回值为x == 0。以下是程序内容。我无法弄清楚我做错了什么,所以欢迎任何建议。

# include <stdio.h>
# include <ios>
# include <iostream>
# include <iomanip>

#include <cstdlib>

using namespace std;
int main(int argc, char** argv) {
   char buffer[30];
   double x = 5.0;
   sprintf(buffer, "%a", 1.2);
   sscanf(buffer, "%la", &x);
   cout << "    Example 1.2 buffer -> " << buffer << endl;
   cout << "    Example 1.2 scanf <- " << x << endl;

   return 0;
}

输出
 Example 1.2 buffer -> 0x1.3333333333333p+0
 Example 1.2 scanf <- 0

在Linux上使用gcc 4.7.2以及在OS X上使用clang测试过,能正常工作。你应该知道%a并不是标准格式。根据man手册的建议,%a不应该与l一起使用。 - Crowman
@PaulGriffiths:%a 可以在 C 2011 标准中找到。第7.21.6.1节,第8段。 - Bill Lynch
@sharth:这是一个C++程序。 - Crowman
2
gcc并没有实现sscanf函数,而是由运行时库来实现。如果你使用的gcc安装使用了Microsoft库,它可能无法完全实现sscanf函数;Microsoft倾向于不太好地支持C99及其后续版本。 - Keith Thompson
代码可以检查 sscanf(buffer, "%la", &x); 的结果,例如 if (sscanf(buffer, "%la", &x) != 1) HandleError();。严谨的代码会执行 int n; if (sscanf(buffer, "%la%n", &x, &n) != 1 || buffer[n]) HandleError(); - chux - Reinstate Monica
显示剩余6条评论
2个回答

2

[更新:Cygwin运行库“newlib”存在处理十六进制浮点数输入的错误。截至2021年4月,已经提交了修复,但尚未发布。]

我看不出你的代码有任何问题。当我在我的Linux系统上编译和运行它时,输出结果是:

    Example 1.2 buffer -> 0x1.3333333333333p+0
    Example 1.2 scanf <- 1.2
%a格式说明符是在C99中引入的,后来并入了C++11。对于sprintf调用,"%a"是一个有效的double类型参数格式,而对于sscanf调用,"%la"是一个有效的double*类型参数格式。
gcc(或g ++)的版本与问题无直接关系。gcc只是编译器;sscanf函数是由运行时库实现的。
当我在Windows 7上的Cygwin下编译和运行您的程序时,我得到与您相同的错误输出。Cygwin使用“newlib” C库,它不同于大多数Linux系统上使用的glibc库。
如果您正在使用MinGW安装的gcc,则我记得它使用Microsoft C库。Microsoft对1990年之后的C标准的支持不太好,因此如果它不能正确实现sscanf"%la"格式,这也就不足为奇了。
请注意,您不需要使用"%la"进行十六进制输入。对于*scanf函数,aefg格式说明符都是等效的,并且都可以接受十进制或十六进制浮点输入。因此,您应该能够使用:
sscanf(buffer, "%lf", &x);

但如果"%la"不能使用,那么"%lf"可能也无法正常工作。

sscanf函数返回一个int类型的结果,表示成功扫描的项目数。您应始终检查该结果。(尽管如果我的Cygwin实验是任何指南的话,在这种情况下它不会有任何好处;sscanf返回1,但仍将x设置为0.0。)

底线:您的代码没问题,但您可能正在使用不支持您尝试执行的操作的运行时库。

这里有另一个程序(它是纯C,但应该可以作为C++使用),它应该提供更多信息。sprintf返回写入的字符数;sscanf返回读取的项目数。

#include <stdio.h>

int main(void) {
    char buffer[40];
    char leftover[40];
    double x = 5.0;
    int sprintf_result = sprintf(buffer, "%a", 1.2);
    printf("sprintf returned %d, buffer = \"%s\"\n", sprintf_result, buffer);
    int sscanf_result = sscanf(buffer, "%lf%s", &x, leftover);
    printf("sscanf returned %d, x = %f", sscanf_result, x);
    if (sscanf_result >= 2) {
        printf(", leftover = \"%s\"", leftover);
    }
    putchar('\n');
    return 0;
}

在我的Linux系统上,我得到了正确的输出,即:
sprintf returned 20, buffer = "0x1.3333333333333p+0"
sscanf returned 1, x = 1.200000

在Cygwin下,我得到了以下输出:
sprintf returned 20, buffer = "0x1.3333333333333p+0"
sscanf returned 2, x = 0.000000, leftover = "x1.3333333333333p+0"

这段文字表明"%lf"格式导致sscanf只消耗了0,其余字符串将被"%s"消耗。这是C90实现的特点(在标准中添加"%a"之前)--但是printf"%a"可以正确工作。
请在您的系统上尝试此操作。
更新7年后:我仍然在Windows 10下看到Cygwin存在相同问题。进一步的实验表明,它不支持任何一个%a格式说明符或十六进制浮点输入。在C99及更高版本中,aefg说明符的行为都相同,接受十进制或十六进制输入,因此,例如,%la格式应该接受像1.0这样的输入。我已向Cygwin邮件列表提交了错误报告:https://cygwin.com/pipermail/cygwin/2021-April/248315.html 更新2:新的补丁已经推送到newlib-cygwin,可以修复此问题,并提供了新的开发者快照。如果一切顺利,我认为Cygwin将很快更新此修复程序。 https://cygwin.com/pipermail/cygwin/2021-April/248323.html

哎呀。我在Cygwin上使用gcc。除非必须,否则我想继续使用它而不转向mingw(你提到了与MSVC库的不兼容问题)。有没有其他选择来规避这个问题? - lostbits
同样的问题。2021年4月21日:确认Windows 10下Cygwin存在问题:我的测试表明它不支持%a格式说明符或十六进制浮点数输入。 - pmor
顺便提一下,在C语言中它被称为“十六进制浮点常量”,而在C++中它被称为“十六进制浮点字面值”。为什么术语没有一致性?感到困惑。 - pmor
@pmor邮件列表上的讨论(请参见此答案底部的链接)表明已经有一个补丁来修复这个问题:https://cygwin.com/pipermail/cygwin/2021-April/248323.html,以及您可以尝试的快照。我预计这将很快包含在Cygwin更新中(当然,我不能保证任何事情)。 - Keith Thompson
@pmor 我不确定,但我怀疑Bjarne Stroustrup只是更喜欢“literal”这个词而不是“constant”。在Stroustrup的《C++程序设计语言》第二版(1991年)和第三版(1997年)之间,术语从“floating-constant”变为“floating-literal”。(我个人认为“literal”更清晰,因为它避免了与“constant expression”和“const”的混淆,但不一致性是不幸的。) - Keith Thompson
1
@KeithThompson 另一个例子:C语言:_整数常量表达式_,C++:_积极常量表达式_。 - pmor

0
sprintf(buffer, "%a", 1.2);

例1.2 缓冲区 -> 0x1.3333333333333p+0
%a格式将数字读入十六进制浮点格式。以此格式表示的数字采用以下形式

0xB.CpN

关于编程的内容:

  • 0x,前缀
  • B,一个十六进制数字,表示规范化二进制小数的最高位为1
  • C,十六进制数字小数部分
  • N,2的N次方

因此:

0x1.3333333333333p+0

是一个表示

1.0011001100110011001100110011001100110011001100110011 x 2^0

这种十六进制格式表示最接近十进制数1.2的双精度浮点数。

关于为什么在您的示例中

Example 1.2 scanf <- 0

使用gcc 4.7.2编译,这个示例可以正常工作,4.8.1也是如此。问题在于您链接的运行时库。


请注意,结尾应该是 ITYM 1.2 而不是 1.3。 - M.M
问题是为什么sscanf调用会将x设置为零。这怎么回答这个问题呢? - Keith Thompson
因为你并没有真正回答问题,所以被踩了。 - Keith Thompson
@KeithThompson已经纠正了,现在两个问题都有答案了。 - 4pie0
两个问题?我只看到一个(暗示的)问题,而你的答案“问题在于你链接的运行时库”似乎不是很有帮助。 - Keith Thompson
@KeithThompson 嗯,我认为这已经足够信息丰富了,可以知道该做什么(而您自己的答案详细说明了可用的库,但这只是在压缩下的信息量)。 - 4pie0

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