将无符号[char,short,int,long]转换为double时结果不一致

5
#include <stdio.h>

int main(int argc, char* argv[]) {
  unsigned char c = 10;
  unsigned short d = 10;
  unsigned int e = 10;
  unsigned long f = 10;
  double g = -c;
  double h = -d;
  double i = -e;
  double j = -f;
  printf("%d %lf\n", c, g);
  printf("%u %lf\n", d, h);
  printf("%u %lf\n", e, i);
  printf("%lu %lf\n", f, j);
}

输出结果
10 -10.000000
10 -10.000000
10 4294967286.000000
10 18446744073709551616.000000

为什么结果不一致,有些类型得到-10,而其他类型得到巨大的值?

2
整数提升将前两个转换为更大的有符号类型(int);对于后两个,只需将一元负号应用于无符号类型。 - dyp
2
为什么这被视为一个糟糕的问题? - dyp
2
@DyP 我也认为这个问题非常好。它写得很清楚,展示了示例代码,并描述了一个不太明显的问题(一元运算符整数提升)。 - Till
这就是为什么在C++中不应该使用无符号类型的原因之一,除非你想要模数算术。想要禁止负值是不够的,我听说过C++委员会成员对他们指定标准库的某些部分使用无符号类型表示遗憾。 - bames53
@chux:已更新。Bruno,请检查我的编辑,并确保我没有搞砸任何东西。 - Keith Thompson
显示剩余3条评论
1个回答

13

一元运算符 - 的操作数会被 提升 ;比 int 窄的类型会被提升为 int 或者 unsigned int

因为(带符号)int 可以容纳所有可以由 unsigned char 表示的值,所以 (unsigned char)10 的值被提升为(带符号)int10。对其取反得到(带符号)int-10,然后将其转换为 double

unsigned int 不会被 "提升" 为 int,因为 int 不能容纳所有这些值。所以表达式 -e 应用了 unsigned int 的取反运算符,显然不能产生负值。结果是 UINT_MAX + 1 - 10,在您的系统上为 4294967286。将其转换为 double 得到您看到的值。

同样,unsigned long 不会被提升,所以 -f 产生 ULONG_MAX + 1 - 10 的值,将其转换为 double 得到的值为 18446744073709551606(264-10)(显然您的系统具有 64 位的 longunsigned long)。将该值转换为 double 会失去一些精度,得到您看到的值。

除了提升规则之外,重要的是要记住 C 表达式的类型(几乎总是)不会受到它所在上下文的影响。无论它被赋给什么,-f 都会产生相同值和类型的结果。


unsigned long 的情况不应该转换为 UINT_MAX + 1 - 10,而是应该转换为 ULONG_MAX + 1 - 10,我相信这只是一个笔误... - twalberg
像“18446744073709551606”转换为“18446744073709551616.000000”这部分很有意思。 - chux - Reinstate Monica

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接