【问题标题】:Why aren't oversized values outputted from pow() coerced the same way if the exponent is a variable?如果指数是变量,为什么从 pow() 输出的超大值不会以相同的方式强制?
【发布时间】:2017-03-28 15:51:27
【问题描述】:

我在做一些按位练习时遇到了这个不寻常的错误。当pow() 的输出被强制转换为unsigned int 时,pow()变量 作为指数调用的结果变为零,而指数为文字时的结果整数 被强制通常 为 0xFFFFFFFF (2^32 - 1)。这只发生在值过大时,在本例中为 2^32。用作指数参数的变量类型似乎不会影响此结果。我还尝试将pow() 的两个调用的输出存储为双精度,然后在引用变量时应用强制;差距依然存在。

#import <math.h>

int main (void) {
  int thirtytwo = 32; // double, unsigned, etc... all yielded the same result

  printf("Raw Doubles Equal: %s\n", pow(2, 32) == pow(2, thirtytwo) ? "true" : "false"); // -> true
  printf("Coerced to Unsigned Equal: %s\n", (unsigned) pow(2, 32) == (unsigned) pow(2, thirtytwo) ? "true": "false"); // -> false

  return 0;
}

出于好奇,我通过 clang/llvm 运行了相同的代码,得到了不同的结果:无论指数是否为变量,将结果强制为 unsigned int 都会产生零(如预期的那样)。

编辑:最大 32 位无符号整数是 2^32 - 1,因此这两种强制输出实际上都正确。我的错误是溢出了整数大小限制。为什么 gcc 本质上向下舍入到最大整数值是一个有趣的好奇心,但并不是特别重要。

【问题讨论】:

  • 如果无符号整数是 32 位长,你怎么认为它们代表 2^32?也许通过点亮不存在的第 33 位?你溢出一个无符号整数,得到 0。
  • 这并不能解释不一致之处。当指数是文字整数时不会发生溢出,例如(unsigned) pow(2, 32) == 0xFFFFFFFF.
  • 对于一个0xFFFFFFFF == 2^32 -1,而不是2^32。当您使用常量而不是变量进行调用时,编译器可以随意优化。不同的编译器甚至可能做不同的事情。
  • 要获得 2 的幂,请改用 1 &lt;&lt; exp

标签: c gcc pow


【解决方案1】:

编译器将使用常量折叠将pow(2, 32) 替换为常量结果; pow(2, thirtytwo) 将在运行时计算。 C11 实际上允许编译时计算比相应的运行时计算更精确 (C11 6.6p5):

如果在翻译环境中计算浮动表达式,则算术范围和精度应至少与在执行环境中计算表达式一样大。

例如,众所周知 GCC 就是这样做的。因此,C 标准并不真正要求第一个打印true (and in practice this does occur in some implementations)。


至于为什么第二个打印false:这是因为pow(2 ^ 32) 不能用32 位的无符号整数表示。 C11 6.3.1.4:

当将实数浮点类型的有限值转换为 _Bool 以外的整数类型时,小数部分被丢弃(即,该值被截断为 0)。 如果整数部分的值不能用整数类型表示,则行为未定义。

因此,第二个打印 false 是由于未定义的行为。与将整数转换缩小为无符号整数不同,对于从浮点数到偶数无符号整数的转换,溢出是明确未定义的。

值得注意的是,我无法让我的 GCC 6.2.0 警告 (unsigned int)pow(2, 32) 的编译时未定义行为。 (我尝试了-lm -Wall -Werror -pedantic -ubsan -Wfloat-conversion -Wconversion -Wextra -std=c11,没有任何输出)。

【讨论】:

  • 在这种情况下,运行时结果显然更准确,您不觉得奇怪吗?我手边没有机器,只有一个 android,但我想知道 (unsigned) pow(2, 32) 是如何编译的。
  • @rici - 我个人不是。如果 GCC 希望优化构建时间,它可能会选择以牺牲浮点常量折叠精度为代价,当然还有其他方式。
  • @storyteller:我同意结果符合标准,您可以放心;我故意没有回应你对 OP 的评论。但是这个答案说编译时更准确,所以我问了 Antti。
  • @rici 回答了那个,第二个因为 UB 而打印为 false。将浮点类型转换为无符号整数时没有 mod-arithmetic。
  • 由于 UB 发生在编译时,这就是所有需要的答案(UB 允许第一次比较的结果也可以)。不过我同意@rici 的观点,编译器发出警告是合理的——它会警告其他类似的错误,那么为什么不在这里发出警告呢?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-21
  • 2016-10-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多