在数学中,同一性(1 + sqrt(2))^2 = 3 + 2*sqrt(2)
是正确的。但在浮点(IEEE 754,使用单精度,即 32 位)计算中,情况并非如此,因为sqrt(2)
它没有精确的二进制表示。
那么使用 的近似值是否sqrt(2)
会为左侧和右侧提供不同的结果?如果是,为什么?对近似值进行平方会显着降低精度吗?
那么,哪个等价表达式给出了最准确的结果?
在数学中,同一性(1 + sqrt(2))^2 = 3 + 2*sqrt(2)
是正确的。但在浮点(IEEE 754,使用单精度,即 32 位)计算中,情况并非如此,因为sqrt(2)
它没有精确的二进制表示。
那么使用 的近似值是否sqrt(2)
会为左侧和右侧提供不同的结果?如果是,为什么?对近似值进行平方会显着降低精度吗?
那么,哪个等价表达式给出了最准确的结果?
当按照 IEEE-754 双精度编写计算时,此恒等式恰好成立。原因如下:
两个正确舍入为双精度的平方根是:
sqrt(2) = 0x1.6a09e667f3bcd * 2^0
(我在这里使用十六进制,因为表示更整洁,并且转换为 IEEE754 格式更容易)。如果没有发生溢出,则乘以二在二进制浮点中是精确的,就像这里的这种情况一样,所以:
2*sqrt(2) = 0x1.6a09e667f3bcd * 2^1
当我们添加三个时,我们得到:
3 + 2*sqrt(2) = 0x1.7504f333f9de68 * 2^2
然而,这不是一个可表示的双精度数(它太宽了一位),所以结果被四舍五入到最接近的可表示数。碰巧这个值正好在两个可表示数字的中间,所以我们选择一个尾随零位的数字:
3 + 2*sqrt(2) = 0x1.7504f333f9de6 * 2^2
现在是计算的另一面。当我们将 1 加到 2 的双精度平方根上时,我们得到:
1 + sqrt(2) = 0x1.3504f333f9de68 * 2^1
这也是可表示的双精度数之间的确切中间情况,并且再次四舍五入到最接近的“偶数”可表示数:
1 + sqrt(2) = 0x1.3504f333f9de6 * 2^1
当这个值被平方时,结果是:
(1 + sqrt(2))*(1 + sqrt(2)) = 0x1.7504f333f9de599cacbc97eaa4 * 2^2
这也不是一个可表示的双精度数。这不是一个精确的中途情况,所以它只是四舍五入到最接近的可表示数字,即:
(1 + sqrt(2))*(1 + sqrt(2)) = 0x1.7504f333f9de6 * 2^2
总结:以两种不同的方式计算这个值会产生两个不同的舍入序列,但最终的结果是相同的。然而,我们只查看了双精度计算;当使用不同的算术类型进行计算时,情况可能并非如此。
然而,一般来说,3 + 2*sqrt(2)
应该期望表达式更准确(在它们不同的情况下),因为对于任何二进制 IEEE-754 类型,它只产生两个舍入(平方根和加法),而(1 + sqrt(2))*(1 + sqrt(2))
产生三个舍入(平方根、加法和乘法)。还应注意,两者之间的差异最多为一位或两位,并且对于您的目的而言可能可以忽略不计。
因为即使0.1 + 0.2 != 0.3
您也不应该指望如此复杂的等式来支持有限精度的浮点数。
由于这些数字存储为四舍五入到一定数量的二进制小数,因此如果该数字(如 0.1)具有无限多的二进制数字,则它们并不准确。因此,使用这些数字的计算结果也不会是精确的,并且预计与计算的精确结果会有小的差异。
那么使用 sqrt(2) 的近似值是否会为左侧和右侧提供不同的结果?如果是,为什么?
从数学上讲,这种等式仅因为这些数字之间的精确关系才有效(它与三角形边的长度有关)。如果以不精确表示的形式添加模糊性,则等式不再成立。平等是一个二元命题,所以问题不再是“哪一方是对的”,而是“这种关系到底是不是真的?”。答案是,“不,这不再是真的”。
对近似值进行平方会显着降低精度吗?
对两个浮点值的每次操作都可能降低它们的准确性。可以保证对于某些数字的非常小的操作子集(具有精确位表示的操作)不会降低准确性。
通常我使用 [(1 + sqrt(2))^2] - [3 + 2*sqrt(2)] < 0.00001 在这种情况下测试相等性(当然在某些情况下我会忽略这种用法)
有没有更好的办法?
评论表示赞赏:)
请注意,仅依靠绝对差异可能会导致问题。它适用于 1 左右的小数,有足够的小数点可以相差 1e-5 或您使用的数字。但想想更大的数字。他们的数字必须存储在有限的空间(尾数)中。并且只存储最重要的数字。这意味着什么?没有剩余空间来存储可以测量差异的数字,例如 1e-5!
总结一下,最好同时使用绝对和相对比较。
bool equal(float a, float b)
{
if (abs(a - b) < eps)
return true;
if (abs(a - b) / max(abs(a), abs(b)) < eps)
return true;
return false;
}
看看好的一面:如果您重新处理该方程式以删除sqrt
s,那么由于您将处理大小合理的整数,因此该方程式将是精确的浮点数;)
不准确通常与需要小数部分(除了 0.5 和 .2 的幂)来表示的数字有关。
回答您问题的另一部分:不,双方的表示sqrt(2)
确实相同。在您开始对两边的相同数字应用(不同)操作之前,不会引入错误(和差异):加 1 与乘以 2 等。
在 C++ 中为浮点数定义相等比较器的人应该被枪杀:>。许多合理的语言(如 SML)没有浮点比较运算符。我通常使用以下代码:
template < typename T >
inline bool equals( T x, T y, T precision = std::numeric_limits<T>::epsilon() )
{
return abs( x - y ) <= precision;
}
注意:abs 在这里也是一个模板化的函数,epsilon 默认存储在外面。比较中的等号是为了我的目的。
在双精度中,(1 + sqrt(2))^2 = 3 + 2*sqrt(2)
似乎成立。请参阅C 代码。
我要再抛出一个想法——
是的,确实,实数的完全相等在计算机编程中是一个毫无意义的概念。
但在我们的物理现实中,实数的精确相等也是一个毫无意义的概念。
我们物理现实中的整数是计数的结果。我们物理现实中的实数是测量的结果。并且所有测量都包含错误。说两个物理测量值完全相同是无稽之谈。充其量,两个物理测量值,四舍五入到适合测量精度的某种精度水平,是相等的。
当你用尺子测量铅笔的长度时,你会得到最接近 16 英寸的长度。当你用卡尺测量它时,你会得到一个最接近 1000 英寸的长度。现实世界的测量总是包括这种四舍五入。当您在计算机程序中模拟真实世界的测量时,您也需要这样做。
实数相等是仅对数学家有意义的概念。(即使在那里,它也是一个与整数相等不同且更复杂的概念)。
sqrt(2) 没有二进制的精确表示。
sqrt(2) 也没有十进制、十六进制或任何其他 base-n 系统的精确表示;这是一个无理数。
sqrt(2) 的唯一精确表示是 sqrt(2)。或者,作为方程 x 2 = 2 的解。
在比较浮点值时,我发现最好将差异的绝对值与给定的容差进行比较。你总是可以指望这一点。
一般来说,双方会给你不同的结果。浮点数学不满足交换和关联属性。涉及许多因素,包括编译器选项和硬件。
对于您的方程式,您可能会找出哪一侧更准确(我的猜测乘以一侧),但如果您决定使用不同的值,它通常不会成立,即对于某些值,一侧可能更准确,而other side 对于其他值更准确。
在您的情况下,平方不应显着影响结果。
那么使用 sqrt(2) 的近似值是否会为左侧和右侧提供不同的结果?如果是,为什么?对近似值进行平方会显着降低精度吗?
加法和乘法都有误差近似。乘法是经验性的,尤其是在嵌套时。
以下不是准确的表示,但有助于理解我的观点:
example of addition:
(float1 * float2 + float3)
float1 * float2 + float3 + mult_approximation + add_approximation
example multiplication
(float1 * (float2 + float3))
(float1 * (float2 + float3 + add_apporiximation)
float1 * (float2 + float3) + add_approximation * float1 + mult_approximation
这是因为表示像 sqrt(x) 这样的连续(无限)函数不能完全在离散(有限)状态机上完成。相反,连续函数通过从 0 到 n 的泰勒级数展开转换为离散函数,其中 n 是您可以表示的最高数字(在本例中为 2^32)。因为您不能在计算机上将总和从 0 取到无穷大,所以您会留下一些剩余的错误。可以计算此误差,以便您可以确定离散函数与连续函数的接近程度。
有关所涉及方程的更多信息和漂亮的 TeX 表示: http ://en.wikipedia.org/wiki/Taylor_series
令人惊讶的是,如果由于某种原因您需要准确表示非有理数(提示:您可能不需要),您可以做一些事情:连分数算术。这个想法可以追溯到 1972 年,并且归功于超级黑客 Bill Gosper - 谷歌搜索。顺便说一句,这个想法的更高级方面是当前数学研究的问题。参见例如这篇论文。
通常,浮点运算精确到 FLT_EPSILON,即在最低有效位内,对于 IEEE 32 位浮点数,它是 2 -23。