【问题标题】:gcc: printf and long double leads to wrong output. [C - Type conversion messes up]gcc: printf 和 long double 导致错误的输出。 [C - 类型转换搞砸了]
【发布时间】:2011-08-20 20:38:15
【问题描述】:

我是 C 的新手。我尝试为 Vector 编写函数,但一定有什么问题。
代码如下:

/* Defines maths for particles. */

#include <math.h>
#include <stdio.h>

/* The vector struct. */
typedef struct {
    long double x, y, z;
} Vector;

Vector Vector_InitDoubleXYZ(double x, double y, double z) {
    Vector v;
    v.x = (long double) x;
    v.y = (long double) y;
    v.z = (long double) z;
    return v;
}

Vector Vector_InitDoubleAll(double all) {
    Vector v;
    v.x = v.y = v.z = (long double) all;
    return v;
}


Vector Vector_InitLongDXYZ(long double x, long double y, long double z) {
    Vector v;
    v.x = x;
    v.y = y;
    v.z = z;
    return v;
}

Vector Vector_InitLongDAll(long double all) {
    Vector v;
    v.x = v.y = v.z = all;
    return v;
}

Vector Vector_AddVector(Vector *v1, Vector *v2) {
    Vector v3;
    v3.x = v1->x + v2->x;
    v3.y = v1->y + v2->y;
    v3.z = v1->z + v2->z;
    return v3;
}

Vector Vector_AddDouble(Vector *v1, double other) {
    Vector v2;
    v2.x = v1->x + other;
    v2.y = v1->y + other;
    v2.z = v1->z + other;
    return v2;
}

Vector Vector_AddLongD(Vector *v1, long double other) {
    Vector v2;
    v2.x = v1->x + other;
    v2.y = v1->y + other;
    v2.z = v1->z + other;
    return v2;
}

void Vector_Print(Vector *v) {
    printf("X: %Lf, Y: %Lf, Z: %Lf\n", v->x, v->y, v->z); //Before edit: used %ld
}

double Vector_Length(Vector *v) {
    return pow(pow(v->x, 2) + pow(v->y, 2) + pow(v->z, 2), 0.5);
}



int main() {
    Vector v = Vector_InitDoubleXYZ(2.0, 1.0, 7.0); //Before edit: (2.0d, 1.0d, 7.0d);

    Vector_Print(&v);
}

我正在使用 gcc 进行编译。在命令行中运行 vector.exe 会得到以下输出:

X:0,Y:-2147483648,Z:9650176

我不明白为什么会这样。

感谢任何提示(即使是关于我的编码风格或任何可以在代码中做得更好的提示)。
谢谢,

更新:使用 MSVC 编译器工作正常,这似乎是 gcc 的问题。你知道为什么会这样吗?

【问题讨论】:

  • 一旦我在printf 中使用%Lg(或%Lf)而不是%ld,就可以正常工作(gcc 4.5.2)。 (不,%d 不是“双”的意思;它的意思是“整数”。看图。)您的原始代码未定义。如果它适用于 MSVC,那是因为 MSVC 正在做一些超出标准的事情。
  • 很抱歉,它在 MSVC 中与 %Lf 一起工作,但与 %d 不兼容,没有更正它。但这仍然不适用于 gcc。
  • Niklas:那么您需要发布您现在提供给 GCC 的 exact 代码,并指出您正在使用的 GCC 版本和 C 库。我告诉你,我将你的三个 %lds 更改为 %Lf 并且效果很好。 (我怀疑你改变了其他东西。)
  • @Nemo 是否按照现在的代码为您工作?
  • 是的,它工作正常。它看起来也很好,编译时没有警告。它打印X: 2.000000, Y: 1.000000, Z: 7.000000。你确定你真的在编译和运行新代码吗?

标签: c gcc


【解决方案1】:

问题(在修复各种问题后,如果使用整数说明符进行浮点格式化)是您将 GCC 类型与不理解它们的 MSVC 运行时混合。

首先,MinGW 是一个 GCC 编译器,但它使用 MSVC 运行时来提供大部分运行时支持。这对printf() 系列函数意味着什么,只有msvcrt.dll 支持的格式说明符和msvcrt.dll 支持的类型才能工作。但是 GCC 对此一无所知,因此它会传递自己的类型,当然,格式说明符是您在格式字符串中传递的任何内容(尽管 GCC 可能会发出并不真正适用于 @987654327 的警告@ 情况)。有关基于 64 位 int 的一些示例,请参阅 Strange "unsigned long long int" behaviour(我认为 msvcrt.dll 的较新版本可能已经修复了部分或全部 64 位 int 问题)。

您遇到的这个问题的另一部分是 GCC 中的 long double 与 MSVC 中的 long double 类型不同。 GCC 在 x86 或 x64 目标上为 long double 使用 96 位或 128 位类型(请参阅 http://gcc.gnu.org/onlinedocs/gcc/i386-and-x86_002d64-Options.html)。但是,MSVC 使用 64 位类型 - 基本上 long doubledouble 完全相同,对于 msvcrt.dll (http://msdn.microsoft.com/en-us/library/9cx8xs15.aspx):

以前的 16 位版本的 Microsoft C/C++ 和 Microsoft Visual C++ 支持 long double、80 位精度数据类型。然而,在 Win32 编程中,long double 数据类型映射到 double、64 位精度数据类型。 Microsoft 运行时库提供长 double 版本的数学函数只是为了向后兼容。 long double 函数原型与其对应的 double 原型相同,只是 long double 数据类型替换了 double 数据类型。这些函数的长双精度版本不应在新代码中使用。

所以这归结为 GCC/MinGW long double 类型将根本不兼容 msvcrt.dll 中的格式化 I/O。要么切换到将 double 与 MinGW 一起使用,要么如果您需要使用 long double,则必须将值转换为 (double) 以进行格式化 I/O,或者提出自己的格式化例程。

另一种选择可能是在 Cygwin 下使用 GCC 编译器,我认为这将避免依赖 msvcrt.dll 进行 I/O 格式化(以依赖 Cygwin 环境为代价)。

【讨论】:

  • 哇,很好的解释。现在我明白了这个问题。我目前正在安装 Cygwin,谢谢!
  • +1 很好地找到了 MSVC 中 long double == double 的问题。我没有注意到这一点。 (虽然我在数值编程的日子里,长双精度很重要,但主要是在基于 gcc 的编译器上。)
  • 注意:AFAIK 96 位或 128 位 gcc 的 x86 和 x64 大小只是对齐问题。这些类型的算术仍然是 x86 浮点寄存器的 80 位精度。以防万一你真的关心精度。
  • 这肯定需要更多的支持。我想知道为什么从字面上打印1000.00L; 是在打印1.13263e-317
  • 96 或 128 位是分配给 long doubles 变量的内存。长双精度仍然是 80 位宽
【解决方案2】:

您的格式字符串与您的类型不匹配。 %ldlong int 的格式说明符,而不是 long double。使用%Lg

【讨论】:

  • 其实long double需要使用%Lg(我认为)。
  • 输出更诡异。 X: -0, Y: 2.37177e-317, Z: -1#QNAN 使用 %lf 也不起作用。
  • @Niklas R,也许你有一个奇怪的printf 实现?在这里使用%Lg 对我来说很好。 @Chris - 谢谢 - 我已经在 =) 中进行了编辑。
  • @Niklas - d 后缀是什么?也许这些是相关的?我的gccclang 都不明白。在 C 中,浮点常量默认为 double,根据规范,唯一有效的后缀是 flFL
  • @Niklas:MinGW 使用 Microsoft C++ 运行时,这意味着您必须使用 Microsoft 语法,它只接受 %lf%Lf 来打印 long double,而不是 %le%lg。见MSDN
【解决方案3】:

应该是:

void Vector_Print(Vector *v) {
    printf("X: %Lf, Y: %Lf, Z: %Lf\n", v->x, v->y, v->z);
}

f(或 g 或 e 或 G 或 E)表示浮点类型,大写 L 表示long double

这是标准长双精度的 C 和 C++ 说明符。使用小写的 l 可能在某些实现中有效,但为什么要让你的程序变得不那么必要。


注意:对于 scanf 函数系列,说明符略有不同。 %f(和其他)表示浮点数,%lf 表示双精度,%Lf 表示长双精度。

使用 printf 没有浮点说明符。您需要将浮点变量转换为双精度。


注意 2:显然 mingw 有一些不兼容问题,这是由于使用 MSVC 运行时导致 %Lf 出现问题。这很奇怪,因为通常 MSVC 允许 %lf 和 %Lf。

Mingw 有 stdio 的替代实现。您可以通过#defining __USE_MINGW_ANSI_STDIO 为 1 (see) 来启用它。我不能保证它会有所帮助。我不太了解mingw。

更新:Michael Burr 的回答解释了为什么将 MSVC 运行时与 MinGW 一起使用时 %Lf 转换失败。如果仍然需要使用 MinGW,请尝试切换到自己的实现。它使用真正的 long double 类型。

【讨论】:

  • 相同的输出。我目前正在安装 Cygwin,它可能会代替我的 gcc,我希望它能在那里工作。
【解决方案4】:

您是否在编译器中启用了警告?如果编译器发出警告,它可能会提示问题所在。

【讨论】:

  • 是的,警告已启用。没有警告。 :)
  • 抱歉,第一次在这里发帖。
【解决方案5】:

试试这样的:

Vector v;
Vector_InitDoubleXYZ(&v, 2.0d, 1.0d, 7.0d);

该函数定义为:

void Vector_InitDoubleXYZ(Vector *v, double x, double y, double z) {
long double t;
t = x;
v->x = t;
t=y;
v->y = t;
t=z;
v->z = t;
}

【讨论】:

  • 不,您可以按值返回struct。效率不高,但你可以做到。
  • 这有什么好处?我更喜欢返回初始化实例的函数。
  • @Chris:实际上,多亏了 RVO,它通常和传递指针一样高效......
  • @Nemo - 不在这里。 RVO 符合 C++ 标准,但(据我所知)不是 C 标准。
  • @Chris:是的。大多数(全部?)优化 C 编译器已经实现了它,因为它不会影响程序观察到的行为。 RVO 在 C++ 规范中得到特别关注的唯一原因是它修改了观察到的行为(想想“具有副作用的复制构造函数”)。 C 中没有复制构造函数,因此标准无需提及。但是任何体面的编译器都会执行它。
猜你喜欢
  • 2011-05-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-11-29
  • 2014-07-01
  • 1970-01-01
相关资源
最近更新 更多