C语言 在 printf 中如何处理 float/double 到 int 的转换?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2398791/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 04:46:14  来源:igfitidea点击:

How is conversion of float/double to int handled in printf?

c

提问by Sandip

Consider this program

考虑这个程序

int main()
{
        float f = 11.22;
        double d = 44.55;
        int i,j;

        i = f;         //cast float to int
        j = d;         //cast double to int

        printf("i = %d, j = %d, f = %d, d = %d", i,j,f,d);
        //This prints the following:
        // i = 11, j = 44, f = -536870912, d = 1076261027

        return 0;
}

Can someone explain why the casting from double/float to int works correctly in the first case, and does not work when done in printf?
This program was compiled on gcc-4.1.2 on 32-bit linux machine.

有人可以解释为什么从 double/float 到 int 的转换在第一种情况下正常工作,而在 printf 中完成时不起作用吗?
这个程序是在 32 位 linux 机器上的 gcc-4.1.2 上编译的。



EDIT:Zach's answerseems logical, i.e. use of format specifiers to figure out what to pop off the stack. However then consider this follow up question:

编辑:Zach 的回答似乎合乎逻辑,即使用格式说明符来确定从堆栈中弹出的内容。但是,请考虑以下后续问题:

int main()
{

    char c = 'd';    // sizeof c is 1, however sizeof character literal
                     // 'd' is equal to sizeof(int) in ANSI C

    printf("lit = %c, lit = %d , c = %c, c = %d", 'd', 'd', c, c);
    //this prints: lit = d, lit = 100 , c = d, c = 100
    //how does printf here pop off the right number of bytes even when
    //the size represented by format specifiers doesn't actually match 
    //the size of the passed arguments(char(1 byte) & char_literal(4 bytes))    

 return 0;
}

How does this work?

这是如何运作的?

回答by Zach Hirsch

The printffunction uses the format specifiers to figure out what to pop off the stack. So when it sees %d, it pops off 4 bytes and interprets them as an int, which is wrong (the binary representation of (float)3.0is not the same as (int)3).

printf函数使用格式说明符来确定从堆栈中弹出的内容。因此,当它看到 时%d,它会弹出 4 个字节并将它们解释为int,这是错误的(的二进制表示(float)3.0与 不同(int)3)。

You'll need to either use the %fformat specifiers or cast the arguments to int. If you're using a new enough version of gcc, then turning on stronger warnings catches this sort of error:

您需要使用%f格式说明符或将参数转换为int. 如果您使用的是足够新的 版本gcc,则打开更强的警告会捕获此类错误:

$ gcc -Wall -Werror test.c
cc1: warnings being treated as errors
test.c: In function ‘main':
test.c:10: error: implicit declaration of function ‘printf'
test.c:10: error: incompatible implicit declaration of built-in function ‘printf'
test.c:10: error: format ‘%d' expects type ‘int', but argument 4 has type ‘double'
test.c:10: error: format ‘%d' expects type ‘int', but argument 5 has type ‘double'

Response to the edited part of the question:

对问题编辑部分的回应:

C's integer promotion rules say that all types smaller than intget promoted to intwhen passed as a vararg. So in your case, the 'd'is getting promoted to an int, then printf is popping off an intand casting to a char. The best reference I could find for this behavior was this blog entry.

C 的整数提升规则说,当作为 vararg 传递时,所有小于的类型int都会被提升int。所以在你的情况下, the'd'被提升为 an int,然后 printf 弹出 anint并转换为 a char。对于此行为,我能找到的最佳参考是此博客条目

回答by AnT

There's no such thing as "casting to intin printf". printfdoes not do and cannot do any casting. Inconsistent format specifier leads to undefined behavior.

有因为没有这样的事情“铸造intprintf”。printf不做也不能做任何铸造。不一致的格式说明符会导致未定义的行为。

In practice printfsimply receives the raw data and reinterpretsit as the type implied by the format specifier. If you pass it a doublevalue and specify an intformat specifier (like %d), printfwill take that doublevalue and blindly reinterpret it an an int. The results will be completely unpredictable (which is why doing this formally causes undefined behavior in C).

实际上,printf简单地接收原始数据并将其重新解释为格式说明符隐含的类型。如果您向它传递一个double值并指定一个int格式说明符(如%d),printf则将采用该double值并盲目地将其重新解释为 an int。结果将是完全不可预测的(这就是为什么这样做会导致 C 中的未定义行为)。

回答by Chris Lutz

Hyman's answerexplains how to fix your problem. I'm going to explain why you're getting your unexpected results. Your code is equivalent to:

Hyman的回答解释了如何解决您的问题。我将解释为什么你会得到意想不到的结果。您的代码相当于:

float f = 11.22;
double d = 44.55;
int i,j,k,l;

i = (int) f;
j = (int) d;
k = *(int *) &f;         //cast float to int
l = *(int *) &d;         //cast double to int

printf("i = %d, j = %d, f = %d, d = %d", i,j,k,l);

The reason is that fand dare passed to printfas values, and then these values are interpreted as ints. This doesn't change the binary value, so the number displayed is the binary representation of a floator a double. The actual cast from floatto intis much more complex in the generated assembly.

原因是fanddprintf作为值传递给,然后这些值被解释为ints。这不会更改二进制值,因此显示的数字是 afloat或 a的二进制表示double。在生成的程序集中,从float到的实际转换int要复杂得多。

回答by Hyman

Because you are not using the float format specifier, try with:

因为您没有使用浮点格式说明符,请尝试:

printf("i = %d, j = %d, f = %f, d = %f", i,j,f,d);

Otherwise, if you want 4 ints you have to cast them before passing the argument to printf:

否则,如果你想要 4 个整数,你必须在将参数传递给 printf 之前强制转换它们:

printf("i = %d, j = %d, f = %d, d = %d", i,j,(int)f,(int)d);

回答by MtnViewJohn

The reason your follow-up code works is because the character constant is promoted to an int before it is pushed onto the stack. So printf pops off 4 bytes for %c and for %d. In fact, character constants are of type int, not type char. C is strange that way.

您的后续代码有效的原因是字符常量在被推送到堆栈之前被提升为 int。所以 printf 为 %c 和 %d 弹出 4 个字节。事实上,字符常量是 int 类型,而不是 char 类型。C就是这样奇怪。

回答by Peter - Reinstate Monica

It's worth noting that printf, being a function with a variable-length argument list, never receives a float; float arguments are "old school" promoted to doubles.

值得注意的是printf,作为具有可变长度参数列表的函数,永远不会收到浮点数;浮动参数是“老派”提升为双打。

A recent standard draft introduces the "old school" default promotions first (n1570, 6.5.2.2/6):

最近的标准草案首先引入了“老派”默认促销(n1570,6.5.2.2/6):

If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type float are promoted to double. These are called the default argument promotions.

如果表示被调用函数的表达式的类型不包含原型,则对每个参数执行整数提升,并将 float 类型的参数提升为 double。这些被称为默认参数提升。

Then it discusses variable argument lists (6.5.2.2/7):

然后讨论可变参数列表(6.5.2.2/7):

The ellipsis notation in a function prototype declarator causes argument type conversion to stop after the last declared parameter. The default argument promotions are performed on trailing arguments.

函数原型声明符中的省略号表示在最后一个声明的参数之后停止参数类型转换。默认参数提升是在尾随参数上执行的。

The consequence for printfis that it is impossible to "print" a genuine float. A float expression is always promoted to double, which is an 8 byte value for IEEE 754 implementations. This promotion occurs on the calling side; printf will already have an 8 byte argument on the stack when its execution starts.

结果printf是不可能“打印”真正的浮标。浮点表达式总是被提升为 double,这是 IEEE 754 实现的 8 字节值。此促销发生在呼叫方;printf 开始执行时,堆栈上已经有一个 8 字节的参数。

If we assign 11.22to a double and inspect its contents, with my x86_64-pc-cygwin gcc I see the byte sequence 000000e0a3702640.

如果我们分配11.22给一个 double 并检查它的内容,使用我的 x86_64-pc-cygwin gcc,我会看到字节序列 000000e0a3702640。

That explains the int value printed by printf: Ints on this target still have 4 bytes, so that only the first four bytes 000000e0 are evaluated, and again in little endian, i.e. as 0xe0000000. This is -536870912 in decimal.

这解释了打印的 int 值printf: 此目标上的 Int仍然有 4 个字节,因此仅计算前四个字节 000000e0,并且再次以小端序,即 0xe0000000。这是十进制的 -536870912。

If we reverse all of the 8 bytes, because the Intel processor stores doubles in little endian, too, we get 402670a3e0000000. We can check the value this byte sequence represents in IEEE format on this web site; it's close to 1.122E1, i.e. 11.22, the expected result.

如果我们反转所有 8 个字节,因为 Intel 处理器也以小端字节序存储双精度数,我们得到 402670a3e0000000。我们可以在这个网站上检查这个字节序列以 IEEE 格式表示的值;它接近 1.122E1,即 11.22,这是预期的结果。

回答by Matthew Flaschen

printf uses variable length argument lists, which means you need to provide the type information. You're providing the wrong information, so it gets confused. Hyman provides the practical solution.

printf 使用可变长度参数列表,这意味着您需要提供类型信息。你提供了错误的信息,所以它会变得混乱。Hyman提供了实用的解决方案。