为什么printf(“%c”,0.21)的结果是'ß'?

eni9jsuy  于 11个月前  发布在  其他
关注(0)|答案(4)|浏览(83)

下面是我的C代码。当我为了学习格式说明符(尤其是%c)而玩弄代码时:

#include <stdio.h>

void main()
{
    double a = 0.21;
    printf("%c", a);
}

字符串
我碰巧遇到了这样的情况,即使我在printf()函数中为格式说明符%c传递一个浮点值作为参数,编译器(我的编译器是gcc)也会以某种方式将值0.21转换为十进制值223,后者对应于ASCII字符ß
对于a = 0.22,输出为:),其ASCII表中的十进制值为29
我在VS codeCLionIDE上运行了代码,但结果都是一样的。现在它让我挠头好几天,我都想不明白。
我想知道值0.210.22如何转换为十进制的22329,或者它们如何分别对应于ASCII 223和29,即ß)
由于值0.21,0.22不对应于任何ASCII码,所以我希望程序不打印任何内容。
但根据输出,我认为这可能与二进制文件有关。
二进制中的0.21等于0.0011011100001010001110111011100...
& 223是11011111
二进制的0.22等于0.0011100001010001110101110000101...
& 29是00011101
而且我找不到任何转换模式。

v8wbuo2f

v8wbuo2f1#

通过float(在本例中自动转换为double)如果%c的类型是int或升级为int的更小类型并使用该值,则printf会查找程序将其传递的参数。在您的情况下,该值恰好是223或29,具体取决于在某些情况下,它可能是不同CPU上的其他东西,或者在程序中任何不相关的更改之后,甚至只是在不同的时间或地点.行为是未定义的,你也可能没有输出或程序崩溃(不太可能,但不是不可能)。
使用编译器警告来尝试和检测此类问题(gcc -Wall -Wextra -Werror),避免绞尽脑汁试图理解未定义的行为。

dpiehjr4

dpiehjr42#

我的编译器是GCC
最有可能的是你正在使用X86-64系统。这种架构有单独的浮点和普通registers。当printf试图获取%c的寄存器时,它读取普通寄存器,但, a设置浮点寄存器。
%c的值与a无关。最有可能的是,它是main初始化后剩余的寄存器值,如_startenviron

cx6n0qe3

cx6n0qe33#

为%c传递float或double有未定义的行为。
如果我们可以定义“为什么”,那么0.21的结果就不是“未定义的行为”了。
但是我们可以假设printf的实现可能是二进制的--将其转换为int,并且结果值确实取决于CPU arch,result为“ss”。

eimct9ow

eimct9ow4#

对于a = 0.22,输出为:),其ASCII表中的十进制值为29
这是不正确的。")"的ASCII码在十进制中是41。在十六进制中是29。41(2916)是用于在double中编码0.22的位的低字节,如下所示。此外,您看到的0.21的字符""在某些字符编码系统中被编码为225(E116),而225是用于将0.21编码为double的比特的低字节。
作为一个简单的测试,您可以将a设置为0x1p52 + x,其中x是从0到255的任何整数,包括0和255。由于double数字是编码的,0x1p52 + x将在低字节中产生x的编码。例如,如果这是您的系统上发生的情况,使用a = 0x1p52 + 88将打印"X",ASCII码为88的字符。
下面是源代码中0.22产生的double如何以最常用于double的格式表示,IEEE-754 binary64:

  • 0.22在二进制中是0.00111000010100011110101110000101000111101110000101000111110111000010100011111011100000111110000011111000001111100111101110111011101110111101111011110111101111101110111111011111011111101111101111111011101111011111110111111101111110111101111
  • 我们通过分离指数因子2 * e * 和有效数(浮点表示的小数部分)来对其进行归一化,选择指数以使有效数至少为1但小于2(二进制为10):1 2 − 3·1.1100001010001111010000111101000011110100001111100000111110000011111000001111100000111000011111000001111100011111001110110111011011011101101101110111011101110111011101110111011101110111011101110111011
  • 该格式使用53位有效数,因此有效数被舍入为53位。有效数的前53位是1.11000010100011110111000010100011110000101000。接下来的位开始111101011.由于这些开始以1开始并包含进一步的1位,尾部大于53位中最低有效位的1/2,因此数字被向上舍入。将被编码的有效位是1.110000101000111101110000101000111101110000101001。
  • 该数字使用三个字段进行编码:一位用于指示符号,11位用于指数,52位用于有效数。
  • 符号位为0,表示"+"。
  • 指数的编码方式是加上1023并转换为11个二进制数,− 3 + 1023 = 1020,即二进制数为0111111100。
  • 从指数编码中可以知道有效数的前导位是1(保留的零代码用于次正常有效数)。有效数的其余52位用于有效数字段。
  • 所以结果位是0 0111111100 11000010100011110111000010100011110111000010101001。
  • 以8位为一组,即0011111 11001100 00101000 11110101 11000010 10001111 01011100 00101001。
  • 十进制的话,是63,204,40,245,194,143,92,41。

注意最后一个字节是41,与您看到的")"匹配。当%c被错误使用时,可以看到的一个行为是使用了传入参数的一个字节的值。例如,调用例程可能会传递用于编码double数字的八个字节,但是printf应该是int的四个字节,(%c接受一个int值,它实际上打印了一个字节)。所以printf从它期望找到int的地方接受了四个字节,将这四个字节转换为unsigned char,并打印结果字符。C标准没有定义这种行为,但它是一个潜在的结果。
对于0.21,编码的double的字节是63,202,225,71,174,20,122,225。您报告的是223,而不是225,但我怀疑您的系统使用的字符编码中的""是225,而不是223。例如,code page 852,在E116(225)点有""。
现在double的字节被当作整数参数是不常见的。它本质上需要在堆栈上传递参数,而不是在处理器寄存器中传递参数,因为今天的处理器通常使用单独的寄存器来处理浮点和整数类型。

脚注

1该格式对指数的大小有限制。这里不适用,因此不详细介绍。当指数太高时,转换为double会导致无穷大的表示。当指数太低时,有效数不会被归一化,并且使用特殊的指数代码来表示低于正常值。

相关问题