Weird outcome when subtracting doubles
Possible Duplicate:
Why is floating point arithmetic in C# imprecise?
我一直在处理一些数字和C,下面的代码行会产生一个不同于预期的数字:
1
| double num = (3600.2 - 3600.0); |
我期望num为0.2,但结果却是0.19999999998181。是否有任何理由产生一个接近但仍然不同的小数?
- 嗯,这里也是。不是你的机器。
- stackoverflow.com/questions/1498296/…
- stackoverflow.com/questions/753948/…
- 每天,当一些新手问这个问题时,我的一小部分就死在里面…
- @麻风病人-很快,我们一定会想你的!
这是因为double是浮点数据类型。
如果你想要更高的精度,你可以改用decimal。
decimal的字面后缀是m,因此要使用decimal算术(并生成decimal结果),可以将代码编写为
1
| var num = (3600.2m - 3600.0m); |
注意,使用decimal有缺点。它是一个128位数据类型,而64位数据类型的大小与double的大小不同。这使得它在内存和处理方面都更加昂贵。它的范围也比double小得多。
- 这与你有多精确无关(当然除非你有无限精确)。正是从一个基到另一个基的转换产生了这个。
- 关于IEEE754类型的误报何时停止?这不是不精确的类型!它是一个精确的类型,但它只能表示有限的数字范围。所有未精确表示的数字都是近似值,这是导致错误的原因。如果你只想表达两种力量,在类型的范围内,你永远不会失去一个浮点精度。
- @Adamralph-这对十进制也是不正确的。decimal是一个浮点类型,但它是以10为基数的,所以通常以10为基数进行算术运算。但是,尝试使用1/3进行计算,十进制将失去精度,尽管使用96位尾数,它的损失将比System.Double小得多。
- 好吧,我从答案中去掉"不精确"。这是我所证明的效果,而不是根本原因
- @codekaizen——诚然,我并没有详细地研究过这个问题,但我不确定您是否认为system.decimal是浮点类型。从类型的msdn条目的第一行-"decimal关键字表示128位数据类型。与浮点类型相比,decimal类型具有更高的精度和更小的范围"—这意味着它不是浮点类型
- @阿达姆拉普-我不知道我和其他开发者争论过多少次。;)您需要进一步阅读……"十进制数是一个浮点值,它由一个符号、一个数值(其中值中的每个数字都在0到9之间)和一个比例因子组成,该比例因子指示分隔数值的整数部分和小数部分的浮点位置。"msdn.microsoft.com/en-us/library/system.decimal.aspx
- 请注意,十进制的混淆通常在基数附近…我也很困惑,直到我第三次看医生。System.Single和System.Double是二进制(以2为基数)浮点类型,System.Decimal是十进制(以10为基数)浮点类型。这使得10次幂的计算精确到十进制,其中它们是单次幂和双次幂的近似值。
- @阿达姆拉普,codekaizan是绝对正确的。十进制有一个…小数点,当这个小数点的位置可以移动时(从上面的msdn链接中),十进制值的二进制表示包括一个1位符号、一个96位整数和一个用于除以96位整数并指定其小数部分的比例因子。比例因子是隐式的数字10,它的指数范围从0到28"…它是一个浮点小数。
- 好的,我理解您的意思,我接受十进制是浮点数据类型。我引用的这句话似乎非常误导人。然而,我的答案(虽然不一定是最好的答案)仍然存在——我不认为这是在说任何错误的话。在目前的情况下,我不明白为什么任何人都觉得有必要投反对票。
- @阿达姆拉普-嗯,最初,它是非常误导,你已经编辑了它相当多。但目前它仍存在一些问题,因为十进制是128位,double是64位,所以十进制更好。当然,使用十进制类型可以获得更高的精度,但这不是一个完美的解决方案(仍然可以有舍入误差),而且严格来说,这并不是因为位数(有效位的大小很重要)。减法仍然可能有灾难性的错误,但是您将小数表示为最佳解决方案的方式不包含任何有关此问题的警告。
- @Adamralph-我同意类型的msdn条目具有误导性。它应该说"decimal关键字表示128位的base-10浮点数据类型。与IEEE754二进制浮点类型相比,十进制类型具有更高的精度和更小的范围,但在减去与其二进制类型几乎相等的数字时,仍然会遇到相同的舍入错误和灾难性取消。
- @codekaizen-我提到十进制是128位,而不是64位,这是与双精度相比的一个缺点,而不是它成为更好选择的原因。我会尽量让这更清楚些。
- @阿达姆拉普-但这不是一个严格的缺点,这就是重点。这也不一定是一种优势。是的,在这个特定的情况下,它使类型更精确,因为它有96位有效位,但它可能有124位指数和4位有效位,然后即使它有128位,也不会比双倍更好。单方面宣称128位更好,甚至更高的精度更好(严格来说并不意味着更高的精度),忽略了浮动点类型的细微差别,并使人们永远误解,这就是我投反对票的原因。
这是有原因的。
原因是,在双数据类型的情况下,数字存储在内存中的方式不允许对数字3600.2进行精确表示。它也不允许数字0.2的精确表示。
0.2的二进制表示是无限的。如果要将它存储在内存或处理器寄存器中,为了执行一些计算,将存储一些接近0.2的有限表示数。如果您运行这样的代码,可能不太明显。
1
| double num = (0.2 - 0.0); |
这是因为在这种情况下,所有可用于表示双数据类型中数字的二进制数字都用于表示数字的小数部分(只有小数部分),并且精度更高。如果将数字3600.2存储在double类型的对象中,则某些数字用于表示整数部分-3600,而表示小数部分的数字较少。精度较低,事实上存储在内存中的分数部分与0.2的差别足够大,在从双精度转换为字符串后,它变得明显。
将您的类型改为decimal:
1
| decimal num = (3600.2m - 3600.0m); |
你也应该读这个。
见维基百科
无法更好地解释。我也可以建议阅读每一个计算机科学家应该知道的关于浮点运算的知识。或者参阅stackoverflow的相关问题。