为什么将 (int)(33.46639 * 1000000) 转换为整数后返回的值是 33466389?

18

(int)(33.46639 * 1000000) 返回 33466389

为什么会这样呢?


你期望发生什么? - SLaks
一个很好的起点:http://docs.sun.com/source/806-3568/ncg_goldberg.html(每个计算机科学家都应该了解浮点算术的知识) - mjv
5
@ Reverend Gonzo:你是不是想说“33466390”? - Mark Carpenter
3
请告诉我们您使用的语言,这会有所帮助。您可能正在使用将33.46639视为浮点类型而不是十进制类型的语言。 - Peter Recore
3
@Peter,有人移除了C#标签,不知道为什么。 - John Farrell
显示剩余2条评论
7个回答

31

浮点数计算并非完美。 每个程序员都应该知道 关于它的相关内容。

许多人认为浮点运算是一门神秘的学科,这令人感到惊讶,因为在计算机系统中,浮点数无处不在。几乎每种语言都有浮点数据类型;从个人电脑到超级计算机,都配备了浮点加速器;大多数编译器将被调用来编译浮点算法;几乎所有操作系统都必须响应浮点异常,如溢出。本文介绍了浮点数的那些方面,对计算机系统设计者具有直接影响。它从浮点表示和舍入误差的背景开始,继续讨论IEEE浮点标准,并以许多例子结束,说明计算机制造商如何更好地支持浮点数。

...

将无限多的实数压缩到有限数量的位中需要近似表示。尽管有无限多个整数,在大多数程序中,整数计算的结果可以存储在32位中。相比之下,对于任何固定数量的位,大多数实数计算将产生无法使用那么多位精确表示的量。因此,浮点数计算的结果通常必须进行舍入,以适应其有限表示形式。这种舍入误差是浮点计算的特征。


1
浮点运算既普遍又复杂,但这并不能回答问题(除非你认为链接到一个80页的文件,在其中某个地方有答案)。 - Hank
2
@Henry - 关键点在于链接文章的标题。每个程序员都应该知道这一点,如果他们不知道,就应该阅读这篇文章。(好吧,也许不是所有80页...) - Stephen C
9
“+1:链接到一个包含答案的80页论文是标准答案。这个问题以一种形式或另一种形式被频繁地提出。这篇论文就是答案。所有的问题都是重复的。我们不需要再次重复这些信息。” - S.Lott

5

双精度浮点数不是精确的,因此内部的33.46639实际上存储为33.466389。

编辑:正如Richard所说,这是浮点数据(以二进制形式存储在有限的位数中),因此并非完全如此。


1
或者是33.4668885,或者是接近该值的其他值(取决于硬件),二者都是针对这个值的四舍五入结果。 - DaveE
1
嗯,以上都不是。它是以2为基数的。大多数这样的数字在十进制下无法准确表示。(无论如何都不能使用无限重复的数字序列。类似于1/3必须在十进制中表示为0.33333 [inf]。) - Richard Berg
2
@Richard:错误。任何二进制数都可以在十进制中精确地表示为一个非重复的小数。(因为十是二的倍数) - SLaks
1
顺便提一下,它确切地是 33.46638999999999697365637985058128833770751953125 - Stephen Canon
1
@Richard Berg:大多数实数不能用浮点数表示。所有可以用N位小数(二进制?)点精确表示的数字都可以用N位小数点精确表示。 - David Thornley
显示剩余4条评论

3
1994年底的新年前夜,英特尔公司的CEO安迪·格鲁夫度过了一个非常成功的一年,因为Pentium处理器的推出大获成功。于是他走进一家酒吧,点了一杯约翰尼沃克绿标威士忌双倍浓度。
调酒师端上酒来说:“先生,这是20美元。”
格鲁夫放下一张20美元的钞票,在柜台前看了一会儿,然后说:“不用找零了。”

http://en.wikipedia.org/wiki/Pentium_FDIV_bug


2
原因是33.46639将被表示为略小于该数字的值。 将其乘以1000000将得到33466389.99999999。 使用(int)进行类型转换将仅返回整数部分(33466389)。 如果您想要“正确”的数字,请在类型转换之前尝试round()。

4
哇!……不不不不不。如果你想要“正确”的答案,就不能使用浮点数算术。 - Reverend Gonzo
4
不行。如果你想要“正确”的答案,就不能使用二进制浮点数算术。请使用decimal类型,它使用十进制浮点数算术,这样它将按照你的期望工作。 - Gabe
1
33.46639是“正确”的答案。问题在于提问者没有问对问题。 - Stephen Canon
@gabe:我假设是C语言,而不是C#。从问题中并不清楚。 - Peter K.
Peter:我是在回应Gonzo的话,他说你不能使用浮点运算。我用decimal作为一个浮点类型的例子,它会给出正确的答案。 - Gabe
显示剩余2条评论

1
如果你想知道为什么它不变成33466390,那是因为double没有无限精度,这个数字不能在二进制中被准确表示。
如果你用decimal替换double(int)(33.46639m * 1000000)),它将等于33466390,因为decimal是以十进制计算的。

这些问题固有于浮点数,不仅限于二进制浮点数。当然,十进制可以正确计算33.466391000000,但仍然存在1/33 != 1和pow(sqrt(2), 2) != 2的情况。 - dan04
是的,但他的具体问题是由二进制引起的。 - SLaks

1

因为33.46639无法在有限数量的二进制数字中精确表示。 33.46639 * 1000000的实际结果为33466389.9999999962747097015380859375。强制转换将其截断为33466389。


尝试翻译:无法用有限数量的分数二进制位精确表示 - Ben Voigt

-1
你得到不同结果的原因是你使用了“cast”。
(int)(33.46639 * 1000000) 返回 33466389
^^^^^
用“int”类型来转换结果...当乘在一起并转换为“int”时,整数类型会向上或向下舍入...不要依赖浮点数的精度足够高...Skeet在他的网站这里这里发布了一个很好的介绍...

强制类型转换本身并不会导致信息丢失。存在任意精度库。 - Richard Berg
我猜他是在问:“为什么我的结果中出现了8?”将数字乘以1000000相当于将小数点向右移动6位,应该得到“33466390”,但他得到的结果不是这样的。不过你的答案一开始也是我想到的,直到我再次阅读问题。 - Mark Carpenter
@Richard - 将floatdouble值强制转换为int会丢弃小数部分,因此您会丢失信息。 - Seth
Seth,说得对。我的意思是,截断是float/double的一个特定功能(通过CLI规范)。它不是C#强制转换运算符固有的,就像Tommie似乎暗示的那样。 - Richard Berg

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