Python:
machine_epsilon = np.finfo(float).eps
first_variant = 1 + machine_epsilon + machine_epsilon/2
second_variant = 1 + machine_epsilon/2 + machine_epsilon
print ('%.20f' % first_variant)
print ('%.20f' % second_variant)
三:
double eps2 = DBL_EPSILON;
printf("1 + eps / 2 +eps %.20f n", 1. + eps2 / 2. + eps2);
printf("1 + eps +eps/2 %.20f n", 1.+ eps2 + eps2 / 2.);
它导致first_variant
的1.00000000000000044409
和second_variant
的1.00000000000000022204
,即分数部分多了2
。谁能解释这一点?
1 + machine_epsilon + machine_epsilon/2
不等于
1 + machine_epsilon/2 + machine_epsilon?
四舍五入在表达式中以不同的值出现,导致不同的总和。
让我们以较小的步骤并使用"%a"
来查看它。
double eps2 = DBL_EPSILON;
printf("%-17s:%21a + %11a = %21an", "1 + eps/2", //
1.0, eps2 / 2.0, 1. + eps2 / 2.0);
printf("%-17s:%21a + %11a = %21an", "(1 + eps/2) + eps", //
1.0 + eps2 / 2, eps2, 1.0 + eps2 / 2.0 + eps2);
printf("%-17s:%21a + %11a = %21an", "1 + eps", //
1.0, eps2, 1. + eps2);
printf("%-17s:%21a + %11a = %21an", "(1 + eps) + eps/2", //
1. + eps2, eps2, 1.0 + eps2 + eps2 / 2.0);
输出
1 + eps/2 : 0x1p+0 + 0x1p-53 = 0x1p+0
(1 + eps/2) + eps: 0x1p+0 + 0x1p-52 = 0x1.0000000000001p+0
1 + eps : 0x1p+0 + 0x1p-52 = 0x1.0000000000001p+0
(1 + eps) + eps/2: 0x1.0000000000001p+0 + 0x1p-52 = 0x1.0000000000002p+0
考虑 3 个连续的double
值,A = 1.0,B = 下一个最大的double
或 1.0 + eps 和下一个double
C = 1.0 + 2*eps。
确切的1 + eps/2
和介于 A 和 B 之间。 使用典型的舍入(舍入到最接近,并列到偶数),总和四舍五入到最佳double
A(向下)。 A 是偶数,因为它的最低有效位是 0,B 是奇数。
(1 + eps / 2) + eps
和就像1 + eps
或B。
1 + eps
和为 B。
(1 + eps) + eps / 2
和是 B 和 C 之间的一半。 对于典型的舍入(舍入到最接近,并列到偶数),总和舍入为 C(向上)。 C 是偶数,因为它的最低有效位是 0,B 是奇数。
如果2
printf("%dn", FLT_EVAL_METHOD);
,则存在其他可能性。 在这种情况下,此处的所有 C 浮点数学都将使用long double
作为中间结果,然后最终的第 1、2 个变体double
结果将预期相同。
浮点运算有限制,它基于 IEEE 754 标准。由于您需要对数字进行四舍五入才能显示它们,因此100/3 = 33.333333333333336
举个例子,但在现实世界中,它并不33.333333333333336
。我的意思是它不仅适用于机器 epsilon,让我们举个例子
print(1 + 1.05/2 + 1.05,1 + 1.05 + 1.05/2)
>>> (2.575, 2.5749999999999997)
print(1 + 1.05/2 + 1.05,1 + (1.05 + 1.05/2))
>>> (2.575, 2.575)
第二个示例给出了相同的输出,(1.05 + 1.05/2)
加 1
你得到不同的输出,因为计算顺序,解释如下,
a = 1 + machine_epsilon
print(a, a + machine_epsilon/2)
>>> (1.0000000000000002, 1.0000000000000004)
a = 1 + machine_epsilon/2
print(a,a + machine_epsilon)
>>> (1.0, 1.0000000000000002)
因此,您应该考虑添加括号,
import numpy as np
machine_epsilon = np.finfo(float).eps
first_variant = 1 + machine_epsilon + machine_epsilon/2
second_variant = 1 + (machine_epsilon/2 + machine_epsilon)
print(first_variant,second_variant)
>>> (1.0000000000000004, 1.0000000000000004)
欲了解更多详情,请参阅, 浮点数学坏了吗?
浮点类型,如 C 中的double
或float
,具有有限的精度(例如,双精度 IEEE 754 二进制 64 的 53 位有效位),因此它们不能表示所有实数,并且它们所涉及的数学运算不具有精确计算中预期的所有属性,特别是它们不是可交换的。
OP 正在尝试评估
1 + machine_epsilon + machine_epsilon/2
这很有挑战性,因为
该数字是不能用
double
(或浮点数)准确表示的数字之一,而1 + machine_epsilon
和1 + 2 * machine_epsilon
都可以。计算表达式时,即使硬件可以使用更高的精度执行计算和计算中间值,存储的结果也必须舍入为可表示的值之一。四舍五入到最接近有许多提示,例如远离零的领带或从偶数开始的领带。
在发布的示例中,1 + machine_epsilon/2
四舍五入为1
,然后加上machine_epsilon
,结果变为1 + machine_epsilon
,而1 + machine_epsilon + machine_epsilon/2
四舍五入为1 + 2 * machine_epsilon
。
你可能在学校学到的一件事是,加法是可交换的和关联的:顺序并不重要。 在纯数学中,a + b == b + a,和(a + b) + c== a+(b + c)。
但是,关于我们每天在计算机上使用的有限精度浮点运算,更令人惊讶的事情之一是加法不是可交换的或关联的。 很多时候,顺序确实很重要,这是你看到的惊人结果的部分解释。
例如,有时会发生的情况是,当b
很小时,a + b
最终等于a
- 加上b
最终根本没有显着差异。 但是对于其他一些稍大的值c
,a + c
最终可能会与a
不同,然后值a + c
使得a + c + b
实际上与a + c
不同。
而且,由于在您的情况下,您选择了机器"epsilon"附近的b
和c
,因此您几乎可以保证您将在将会发生如此令人惊讶的舍入结果的区域进行操作。
不过,我无法解释为什么你在C和Python中看到不同的结果。