为什么 VBA 和 Excel 不同意两个单元格是否相等?
Why do VBA and Excel disagree on whether two cells are equal?
我正在尝试比较 table:
中的两个单元格
“MR”列是使用公式=ABS([@Value]-A1)
计算的,以确定“值”列的移动极差。 “值”列中的值未四舍五入。 “MR”列(B3 和 B4)中突出显示的单元格是相等的。我可以在单元格中输入公式 =B3=B4
,然后 Excel 表示 B3 等于 B4。
但是当我在VBA中比较它们时,VBA说B4大于B3。我可以 select 单元格 B3 并将以下内容输入即时 Window ? selection.value = selection.offset(1).value
。该语句的计算结果为 false。
我尝试从公式中删除绝对值,认为这可能与它有关,但 VBA 仍然说它们不相等。
我尝试添加另一行,其中 Value=1.78,因此 MR=0.18。有趣的是,新行 (B5) 中的 MR 等于 B3,但 不 等于 B4。
然后我尝试增加 A4 的小数位数以匹配其他值,现在 VBA 说它们 相等。但是当我将绝对值加回公式时,VBA 再次表示它们 不 相等。我再次删除了绝对值,现在 VBA 说它们 不 相等。
为什么 VBA 告诉我单元格不相等,而 Excel 说它们相等?我怎样才能通过 VBA 向前可靠地处理这种情况?
问题是 IEEE 754 Standard for Floating-Point Arithmetic 的设计不精确。几乎每一种编程语言都因此而受到影响。
IEEE 754 是一个极其复杂的主题,当您研究它几个月并相信自己完全理解时,您只是在自欺欺人!
Accurate floating point value comparisons 困难且容易出错。在尝试比较浮点数之前仔细考虑!
Excel 程序通过在应用程序端作弊来解决这个问题。另一方面,VBA 忠实地遵循 Double Precision 的 IEEE 754 规范(binary64)。
Double 值在内存中使用 64 位表示。这 64 位被分成三个不同的字段,用于二进制科学记数法:
- SIGN位(1位表示值的符号:pos/neg)
- EXPONENT(11 位,值偏移 +1023)
- MANTISSA(53 位,存储的 52 位 + 隐含的 1 位)
数学运算如下:存储值 = SIGN VALUE * 2^UNBIASED EXPONENT * MANTISSA
请注意,符号位中的值 1 表示负符号值 (-1),而 0 表示正符号值 (+1)。公式为SIGN VALUE = (-1) ^ (sign bit)
.
问题总是归结为同一件事。
The vast majority of real numbers cannot be expressed precisely
within this system which introduces small rounding errors that propagate
like weeds.
使用您的示例数字...
1.24 用以下二进制表示:
符号位 = 0
指数 = 01111111111
尾数 = 0011110101110000101000111101011100001010001111010111
完整 64 位的十六进制模式恰好是:3FF3D70A3D70A3D7。
精度完全来自 53 位尾数,二进制的精确十进制值为:
0.2399999999999999911182158029987476766109466552734375
在这种情况下,“1”的值是隐含的,因此完整的十进制值正好是:
1.2399999999999999911182158029987476766109466552734375
现在请注意,这不是精确的 1.24,这就是整个问题。
让我们检查 1.42:
符号位 = 0
指数 = 01111111111
尾数 = 0110101110000101000111101011100001010001111010111000
完整 64 位的十六进制模式恰好是:3FF6B851EB851EB8。
使用隐含的“1”,完整的十进制值存储为:
1.4199999999999999289457264239899814128875732421875000
同样不精确 1.42
现在,让我们来看看 1.6:
符号位 = 0
指数 = 01111111111
尾数 = 1001100110011001100110011001100110011001100110011010
完整 64 位的十六进制模式恰好是:3FF999999999999A。
Notice the repeating binary fraction in this case that is truncated
and rounded when the mantissa bits run out? Obviously 1.6 when
represented in binary base2 can never be precisely accurate in the
same way as 1/3 can never be accurately represented in decimal base10
(0.33333333333... ≠ 1/3).
使用隐含的“1”,完整的十进制值存储为:
1.6000000000000000888178419700125232338905334472656250
不完全是 1.6,但比其他更接近!
现在让我们减去完整的存储双精度表示:
1.60 - 1.42 = 0.18000000000000015987
1.42 - 1.24 = 0.17999999999999993782
所以如你所见,它们根本不相等。
解决此问题的常用方法是 threshold testing,基本上是检查两个值是否足够接近...这取决于您和您的要求。预先警告,有效的阈值测试方式比乍一看更难。
这是一个帮助您开始比较两个双精度数的函数。它可以很好地处理许多情况,但不是所有情况,因为没有函数可以。
Function Roughly(a#, b#, Optional within# = 0.00001) As Boolean
Dim d#, x#, y#, z#
Const TINY# = 1.17549435E-38 'SINGLE_MIN
If a = b Then Roughly = True: Exit Function
x = Abs(a): y = Abs(b): d = Abs(a - b)
If a <> 0# Then
If b <> 0# Then
z = x + y
If z > TINY Then
Roughly = d / z < within
Exit Function
End If
End If
End If
Roughly = d < within * TINY
End Function
这里的想法是让函数return True
如果两个Double大致相同在一定范围内:
MsgBox Roughly(3.14159, 3.141591) '<---dispays True
Within margin 默认为 0.00001,但您可以传递您需要的任何 margin。
虽然我们知道:
MsgBox 1.60 - 1.42 = 1.42 - 1.24 '<---dispays False
考虑一下这个的效用:
MsgBox Roughly(1.60 - 1.42, 1.42 - 1.24) '<---dispays True
@chris neilsen 链接到一个有趣的 Microsoft page 关于 Excel 和 IEEE 754。
请阅读 David Goldberg 的开创性著作 What Every Computer Scientist Should Know About Floating-Point Arithmetic。它改变了我理解浮点数的方式。
我正在尝试比较 table:
中的两个单元格“MR”列是使用公式=ABS([@Value]-A1)
计算的,以确定“值”列的移动极差。 “值”列中的值未四舍五入。 “MR”列(B3 和 B4)中突出显示的单元格是相等的。我可以在单元格中输入公式 =B3=B4
,然后 Excel 表示 B3 等于 B4。
但是当我在VBA中比较它们时,VBA说B4大于B3。我可以 select 单元格 B3 并将以下内容输入即时 Window ? selection.value = selection.offset(1).value
。该语句的计算结果为 false。
我尝试从公式中删除绝对值,认为这可能与它有关,但 VBA 仍然说它们不相等。
我尝试添加另一行,其中 Value=1.78,因此 MR=0.18。有趣的是,新行 (B5) 中的 MR 等于 B3,但 不 等于 B4。
然后我尝试增加 A4 的小数位数以匹配其他值,现在 VBA 说它们 相等。但是当我将绝对值加回公式时,VBA 再次表示它们 不 相等。我再次删除了绝对值,现在 VBA 说它们 不 相等。
为什么 VBA 告诉我单元格不相等,而 Excel 说它们相等?我怎样才能通过 VBA 向前可靠地处理这种情况?
问题是 IEEE 754 Standard for Floating-Point Arithmetic 的设计不精确。几乎每一种编程语言都因此而受到影响。
IEEE 754 是一个极其复杂的主题,当您研究它几个月并相信自己完全理解时,您只是在自欺欺人!
Accurate floating point value comparisons 困难且容易出错。在尝试比较浮点数之前仔细考虑!
Excel 程序通过在应用程序端作弊来解决这个问题。另一方面,VBA 忠实地遵循 Double Precision 的 IEEE 754 规范(binary64)。
Double 值在内存中使用 64 位表示。这 64 位被分成三个不同的字段,用于二进制科学记数法:
- SIGN位(1位表示值的符号:pos/neg)
- EXPONENT(11 位,值偏移 +1023)
- MANTISSA(53 位,存储的 52 位 + 隐含的 1 位)
数学运算如下:存储值 = SIGN VALUE * 2^UNBIASED EXPONENT * MANTISSA
请注意,符号位中的值 1 表示负符号值 (-1),而 0 表示正符号值 (+1)。公式为SIGN VALUE = (-1) ^ (sign bit)
.
问题总是归结为同一件事。
The vast majority of real numbers cannot be expressed precisely within this system which introduces small rounding errors that propagate like weeds.
使用您的示例数字...
1.24 用以下二进制表示:
符号位 = 0
指数 = 01111111111
尾数 = 0011110101110000101000111101011100001010001111010111
完整 64 位的十六进制模式恰好是:3FF3D70A3D70A3D7。
精度完全来自 53 位尾数,二进制的精确十进制值为: 0.2399999999999999911182158029987476766109466552734375
在这种情况下,“1”的值是隐含的,因此完整的十进制值正好是:
1.2399999999999999911182158029987476766109466552734375
现在请注意,这不是精确的 1.24,这就是整个问题。
让我们检查 1.42:
符号位 = 0
指数 = 01111111111
尾数 = 0110101110000101000111101011100001010001111010111000
完整 64 位的十六进制模式恰好是:3FF6B851EB851EB8。
使用隐含的“1”,完整的十进制值存储为:
1.4199999999999999289457264239899814128875732421875000
同样不精确 1.42
现在,让我们来看看 1.6:
符号位 = 0
指数 = 01111111111
尾数 = 1001100110011001100110011001100110011001100110011010
完整 64 位的十六进制模式恰好是:3FF999999999999A。
Notice the repeating binary fraction in this case that is truncated and rounded when the mantissa bits run out? Obviously 1.6 when represented in binary base2 can never be precisely accurate in the same way as 1/3 can never be accurately represented in decimal base10 (0.33333333333... ≠ 1/3).
使用隐含的“1”,完整的十进制值存储为:
1.6000000000000000888178419700125232338905334472656250
不完全是 1.6,但比其他更接近!
现在让我们减去完整的存储双精度表示:
1.60 - 1.42 = 0.18000000000000015987
1.42 - 1.24 = 0.17999999999999993782
所以如你所见,它们根本不相等。
解决此问题的常用方法是 threshold testing,基本上是检查两个值是否足够接近...这取决于您和您的要求。预先警告,有效的阈值测试方式比乍一看更难。
这是一个帮助您开始比较两个双精度数的函数。它可以很好地处理许多情况,但不是所有情况,因为没有函数可以。
Function Roughly(a#, b#, Optional within# = 0.00001) As Boolean
Dim d#, x#, y#, z#
Const TINY# = 1.17549435E-38 'SINGLE_MIN
If a = b Then Roughly = True: Exit Function
x = Abs(a): y = Abs(b): d = Abs(a - b)
If a <> 0# Then
If b <> 0# Then
z = x + y
If z > TINY Then
Roughly = d / z < within
Exit Function
End If
End If
End If
Roughly = d < within * TINY
End Function
这里的想法是让函数return True
如果两个Double大致相同在一定范围内:
MsgBox Roughly(3.14159, 3.141591) '<---dispays True
Within margin 默认为 0.00001,但您可以传递您需要的任何 margin。
虽然我们知道:
MsgBox 1.60 - 1.42 = 1.42 - 1.24 '<---dispays False
考虑一下这个的效用:
MsgBox Roughly(1.60 - 1.42, 1.42 - 1.24) '<---dispays True
@chris neilsen 链接到一个有趣的 Microsoft page 关于 Excel 和 IEEE 754。
请阅读 David Goldberg 的开创性著作 What Every Computer Scientist Should Know About Floating-Point Arithmetic。它改变了我理解浮点数的方式。