【问题标题】:Why is the result of the C program showing a different result than expected?为什么 C 程序的结果显示的结果与预期的不同?
【发布时间】:2020-11-14 09:28:32
【问题描述】:

当我运行这个程序时:

#include <stdio.h>
int main (void)
{
    float x;
    double y;
    x = - 2147483645.0;
    y = -2147483645.0f; 
    printf("%f, %f", x, y);
return 0;
}

结果是-2147483648.000000,-2147483645.000000

为什么会这样?`

【问题讨论】:

标签: c


【解决方案1】:

浮点数是不精确的,更大的尺寸意味着更高的精度。在这种情况下,double 足够大,可以精确存储数字,但float 不是,这意味着它会打印出错误的值。 Read more here.

【讨论】:

  • Wrong 表示发生了错误。值没有错。这是最近的浮动。
【解决方案2】:

2147483645.0 的值将是 1.111111111111111111111111111101∙2³⁰ 以二进制形式,所以它需要一个 30 位的尾数。但是float 数据类型只提供 23 位尾数,而 double 大约有 52 位。符号单独保存(这也取决于您的平台和编译器,此值适用于标准 x86)。

考虑这个程序:

#include <stdio.h>

int main() {
  float x = -2147483645.0;
  double y = -2147483645.0;

  printf("%f  %X\n", x, *((unsigned*) &x));
  printf("%f  %X%X\n", y, *( ((unsigned*) &y)+1), *((unsigned*) &y));
}

我用 gcc 5.4.0 for x86 编译它并得到输出:

-2147483648.000000  CF000000
-2147483645.000000  C1DFFFFFFF400000

十六进制数字的内部格式见右图:

float x (32 bits in total):
===========================
Sign:     1
Exponent: 100 1111 0 (bias 127 + 31)
Mantissa: 000 0000 0000 0000 0000 0000

double y (64 bits in total):
============================
Sign:     1
Exponent: 100 0001 1111 (bias 1023 + 32)
Mantissa: 1111 1111 1111 1111 1111 1111 1111 0100 0000 0000 0000 0000 0000

我在输出中对数字进行了分组。如上所述,双精度 y 精确存储数字的二进制表示。相反,浮点数x 的尾数为零。这是因为这些位不是简单地切断的。相反,该值会根据多余的位进行四舍五入。这就是为什么你在输出中得到1.0∙2³¹=2147483648

您也可以在these 等网站上试用。

四舍五入由这里的 c 预处理器完成。我不知道如何影响这一点,但您可以在程序中控制舍入模式,如 here 所述:

#include <stdio.h>
#include <fenv.h>
#pragma STDC FENV_ACCESS ON

int main() {
  float x;
  double y = -2147483645.0;
  
  fesetround(FE_TONEAREST);
  x = y;
  printf("FE_TONEAREST:  %f %X\n", x, *((unsigned*) &x));
  
  fesetround(FE_UPWARD);
  x = y;
  printf("FE_UPWARD:     %f %X\n", x, *((unsigned*) &x));
  
  fesetround(FE_DOWNWARD);
  x = y;
  printf("FE_DOWNWARD:   %f %X\n", x, *((unsigned*) &x));
  
  fesetround(FE_TOWARDZERO);
  x = y;
  printf("FE_TOWARDZERO: %f %X\n", x, *((unsigned*) &x));
}

使用-lm 选项编译。这输出

FE_TONEAREST:  -2147483648.000000 CF000000
FE_UPWARD:     -2147483520.000000 CEFFFFFF
FE_DOWNWARD:   -2147483648.000000 CF000000
FE_TOWARDZERO: -2147483520.000000 CEFFFFFF

【讨论】:

  • 因此,如果它使用 23 位尾数,那么我认为它将保存前 23 位,并且每个位都是 1。当我们将该数字 (1.11111111111111111111111 * 2^30) 转换为十进制时相当于我们会得到 2147483520。为什么不是这个结果?
  • 我在帖子中添加了一些进一步的解释:多余的位不只是被切断。该值根据此位进行四舍五入,因此您得到1.0∙2³¹=2147483648(这是2³¹,而不是2³⁰,我的错误)
  • 你第一次是对的 - 它是 1.111111111111111111111111111101∙2^30,而不是 1.111111111111111111111111111101∙2^31。我想我终于明白了。尾数有 23 位,但如果我们需要更多,您需要检查如果您有 24 位尾数,您将放置的第 24 位。如果该位为 0,那么您什么也不做。你有你的 23 位尾数。但如果是 1,则需要在整个尾数上加 1,就像对二进制数进行加法一样。
  • 因此,我的逻辑是(考虑到我的示例中的数字)您拥有:1.1111 1111 1111 1111 1111 111 + 0.0000 0000 0000 0000 0000 001 即 10.0000000000000003000000000000000000000000002 2^31,这是我们的号码。您实际上需要在包括隐藏位在内的整个尾数上加 1。
  • 经过进一步研究,我可以说我在上一条评论中提到的规则不起作用。它确实以某种方式围绕尾数中的位,但我不明白规则。知道四舍五入是如何工作的吗?
猜你喜欢
  • 2015-09-14
  • 2020-04-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多