我的问题:
尝试找出以下C代码段的结果:
#include
int main(int argc, char* argv[])
{
double a = 5.1;
int b = a * 100;
std::cout << b << std::endl;
double c = 6.1;
int d = c * 100;
std::cout << d << std::endl;
}
在Windows上,我使用VS2008 SP1编译并运行了上面的代码,并获得:
509
610
在Linux上,我使用g编译并运行了相同的代码,并得到:
509
609
代码有什么问题?
对不起,我试图为这个问题找到一个标题,所以我可以四处搜寻。但是,我无法命名此问题,因此我在此直接提出。
任何建议都值得赞赏。
这个问题只有十分之一的重复...
我很高兴此链接位于Wiki标签上:docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
Windows上为509610,Linux上为509609。这最终证明Windows比Linux更好吗?
5.1并非完全是5.1。实际上是5.0999999999999996447286321199499070644378662109375。
这就是为什么结果看起来如此怪异的原因。 int到double的转换总是四舍五入,而不是最接近的整数。
@StephenCanon:为什么您认为编译器必须将其四舍五入?
@MSalters:假定正确的默认舍入,大多数理智的编译器就是这种情况。
@StephenCanon:那是不对的。 5.1始终是5.1。但是a不一定是5.1!
@KerrekSB:数字5.1始终为5.1。双精度文字5.1不是5.1。
@StephenCanon:好的,很公平。
@StephenCanon:我在标准中找不到。 C99明显不同:"结果是以实现定义的方式选择的最接近的可表示值,或者紧邻最接近的可表示值的较大或较小的可表示值。"
@MSalters:它不在C或C标准中(超出您所标识的文本);但是,这里所讨论的实现通常是尝试遵循IEEE-754标准的(因此"假定")。
@StephenCanon:也找不到; IEEE-754在外部字符序列上的措词建议您使用9位数字来保证32位值。 en.wikipedia.org/wiki/…
@MSalters:IEEE-754条款5.12:"在程序文本中没有其他规范的情况下,程序文本中的常量从外部字符序列到支持格式的转换时转换,应使用该标准的默认舍入方向和语言定义异常处理。"
c中可能有奇怪的double到int转换行为的重复
@KerrekSB:非常感谢!并且请注意,声明我已经尝试找出问题的名称,因此可以在将其发布到此处之前对其进行搜索。 :)
@stoneyang:只需在该网站上呆大约一周,您就会明白我的意思了:-)(在此期间,您可以回答一些问题并获得一些徽章!好吧...)
@KerrekSB:再次感谢您的提示!
double不是精确类型,如通过应用std::numeric_limits typetrait可以看到的那样:
#include
static_assert(std::numeric_limits::is_exact == false);
因此,涉及双打的计算仅是近似值,您观察到的内容没有错。
您的代码没有问题。
很奇怪,我不知道那是存在的。感谢您的证明工具。
再说一次,3/2也不正确。它只是第一个小数位而不是第53个不精确。
这个答案并没有真正解释两个编译器/系统在行为上的差异,这似乎是问题的核心。 double不是精确的,是的,但这并不意味着在四舍五入时表现不同是可以的。
@ J.N .:问题不在于四舍五入。问题在于,由于实现定义的方式,a的值仅约为5.1,因此我们无法知道。
@MSalters:3/2恰好是1 :-)
@KerrekSB:iso double保证字符串文字的精度为15位,我看不到这里定义的实现;即使由于乘法运算而导致固有的精度损失,也不会那么大。 zh.wikipedia.org/wiki/Double-precision_floating-point_format
@ J.N .:让我们举一个玩具的例子。假设一个虚构的类型只允许精度为5个十进制数字。在某些奥秘体系结构中,按该类型求值的文字5.2可能是5.20003或5.19997,并且将完全兼容。两者都没有首选项,并且四舍五入到五个有效数字时,两者都是相同的。但是,乘以100并截断会得到不同的结果。
@ J.N。 :精度损失是否恰好发生在四舍五入的边缘值上,这与精度的损失无关紧要。在这种情况下,数学表达式5.1 * 100.0必须四舍五入为整数510,但必须将(5.1-ε) * 10.00四舍五入为509
通常,双精度(和浮点数)为二进制,则只能精确表示2的幂的分数。因此,如果小数部分仅由1 / 2、1 / 4之类的项组成, 1 / 8、1 / 16等。浮点数或双精度数将是精确的(当然,除非遇到精度问题)。
现在," 0.1 "是1/10,实际上是1/2 * 1/5。 5不是2的幂,因此" 0.1 "不能用二进制表示,只能近似。
存在十进制浮点类型(在二进制硬件上实现),它们可以精确表示0.1。二进制本质上没有什么坏处。它只是取决于实施问题。
作为Kernighan和Plauger所说的"编程样式元素"的规则之一。
10.0 times 0.1 is hardly ever 1.0
浮动的含义不像数学实数。有一个标准(IEEE 754)如何实现浮点数,但它不是C标准的一部分,而仅在C#中实现。
除非另有说明,否则
double到int的转换是通过截断完成的。在您的情况下,由于四舍五入时精度的损失(以及MSalters指出的一小笔不幸,因为这仅发生在某些" edge "值附近)。
以下是导致编译器/操作系统之间行为不同的几种可能原因:
优化:某些编译器以无限的精度实现编译时浮动操作。由于您的代码很简单,因此编译器可以在编译时以无限的精度进行计算。 (您是否检查了生成的ASM代码?)
使用不同的内部表示形式。由于您正在运行Windows,因此您使用的x86或x86-64 CPU实现了64位以上的浮点数,有关详细信息,请参见此内容。
完全使用其他体系结构(您的OS都是64位的吗?)