我无法在Excel VBA中比较2个双倍
假设我有以下代码
Dim a as double
Dim b as double
a = 0.15
b = 0.01
在对b进行一些操作之后,b现在等于0.6
然而,与双数据类型相关的不精确让我感到头痛,因为
if a = b then
//this will never trigger
end if
你知道如何删除双重类型的尾随不精确吗?
答案 0 :(得分:14)
您无法比较浮点值是否相等。有关如何处理内在错误的讨论,请参阅有关“Comparing floating point numbers”的文章。
除非你事先知道浮点数的绝对范围是什么,否则它并不像比较恒定的误差范围那么简单。
答案 1 :(得分:6)
如果你打算这样做......
Dim a as double
Dim b as double
a = 0.15
b = 0.01
你需要在你的IF语句中添加圆函数,就像这样......
If Round(a,2) = Round(b,2) Then
//code inside block will now trigger.
End If
答案 2 :(得分:5)
比较平等的双打是不明智的。
某些十进制值映射到多个浮点表示。因此,一个0.6并不总是等于另一个0.6。
如果我们从另一个中减去一个,我们可能得到类似于0.00000000051的东西。
我们现在可以将等式定义为具有小于某个误差范围的差异。
答案 3 :(得分:5)
这是我写的一个简单的函数:
Function dblCheckTheSame(number1 As Double, number2 As Double, Optional Digits As Integer = 12) As Boolean
If (number1 - number2) ^ 2 < (10 ^ -Digits) ^ 2 Then
dblCheckTheSame = True
Else
dblCheckTheSame = False
End If
End Function
用以下方式调用:
MsgBox dblCheckTheSame(1.2345, 1.23456789)
MsgBox dblCheckTheSame(1.2345, 1.23456789, 4)
MsgBox dblCheckTheSame(1.2345678900001, 1.2345678900002)
MsgBox dblCheckTheSame(1.2345678900001, 1.2345678900002, 14)
答案 4 :(得分:3)
正如已经指出的那样,许多十进制数不能精确地表示为传统的浮点类型。根据问题空间的性质,最好使用十进制VBA类型,它可以表示十进制数字(基数为10),具有完美的精度,直到某个小数点。这通常用于代表货币,例如通常需要2位十进制精度。
Dim a as Decimal
Dim b as Decimal
a = 0.15
b = 0.01
答案 5 :(得分:1)
货币数据类型可能是一个不错的选择。它处理相对较大的数字,具有固定的四位精度。
答案 6 :(得分:0)
一轮工作? 不知道这是否可以解决所有情况,但是在比较VBA中的舍入double值时遇到了问题。当我将四舍五入后的数字进行比较时,VBA会在if-then compare语句中触发false。 我的解决方法是运行两次转换,首先将double转换为string,然后将string转换为double,然后进行比较。
模拟示例 我没有记录导致这篇文章中提到的错误的确切数字,并且我的示例中的金额当前未触发问题,仅用于表示问题的类型。
Sub Test_Rounded_Numbers()
Dim Num1 As Double
Dim Num2 As Double
Let Num1 = 123.123456789
Let Num2 = 123.123467891
Let Num1 = Round(Num1, 4) '123.1235
Let Num2 = Round(Num2, 4) '123.1235
If Num1 = Num2 Then
MsgBox "Correct Match, " & Num1 & " does equal " & Num2
Else
MsgBox "Inccorrect Match, " & Num1 & " does not equal " & Num2
End If
'Here it would say that "Inccorrect Match, 123.1235 does not equal 123.1235."
End Sub
Sub Fixed_Double_Value_Type_Compare_Issue()
Dim Num1 As Double
Dim Num2 As Double
Let Num1 = 123.123456789
Let Num2 = 123.123467891
Let Num1 = Round(Num1, 4) '123.1235
Let Num2 = Round(Num2, 4) '123.1235
'Add CDbl(CStr(Double_Value))
'By doing this step the numbers
'would trigger if they matched
'100% of the time
If CDbl(CStr(Num1)) = CDbl(CStr(Num2)) Then
MsgBox "Correct Match"
Else
MsgBox "Inccorrect Match"
End If
'Now it says Here it would say that "Correct Match, 123.1235 does equal 123.1235."
End Sub
答案 7 :(得分:0)
如果可能,尝试使用单个值。 转换为Double值会产生随机错误。
Public Sub Test()
Dim D01 As Double
Dim D02 As Double
Dim S01 As Single
Dim S02 As Single
S01 = 45.678 / 12
S02 = 45.678
D01 = S01
D02 = S02
Debug.Print S01 * 12
Debug.Print S02
Debug.Print D01 * 12
Debug.Print D02
End Sub
45,678
45,678
45,67799949646
45,6780014038086
答案 8 :(得分:0)
迟到的答案,但我很惊讶还没有发布解决方案来解决(当前)接受的 answer 中链接的文章中概述的问题,即:
为了解决这个问题,我从 Python 中获得灵感:PEP 485 -- A Function for testing approximate equality 来实现以下内容(在标准模块中):
'@NoIndent: Don't want to lose our description annotations
'@Folder("Tests.Utils")
Option Explicit
Option Private Module
Private Const OVERFLOW_ERR As Long = 6
'Based on Python's math.isclose https://github.com/python/cpython/blob/17f94e28882e1e2b331ace93f42e8615383dee59/Modules/mathmodule.c#L2962-L3003
'math.isclose -> boolean
' a: double
' b: double
' relTol: double = 1e-09
' maximum difference for being considered "close", relative to the
' magnitude of the input values
' absTol: double = 0.0
' maximum difference for being considered "close", regardless of the
' magnitude of the input values
'Determine whether two floating point numbers are close in value.
'Return True if a is close in value to b, and False otherwise.
'For the values to be considered close, the difference between them
'must be smaller than at least one of the tolerances.
'-inf, inf and NaN behave similarly to the IEEE 754 Standard. That
'is, NaN is not close to anything, even itself. inf and -inf are
'only close to themselves.
'@Description("Determine whether two floating point numbers are close in value, accounting for special values in IEEE 754")
Public Function IsClose(ByVal a As Double, ByVal b As Double, _
Optional ByVal relTol As Double = 0.000000001, _
Optional ByVal absTol As Double = 0 _
) As Boolean
If relTol < 0# Or absTol < 0# Then
'sanity check on the inputs
Err.Raise 5, Description:="tolerances must be non-negative"
ElseIf a = b Then
'short circuit exact equality -- needed to catch two infinities of
'the same sign. And perhaps speeds things up a bit sometimes.
IsClose = True
Exit Function
ElseIf IsInfinity(a) Or IsInfinity(b) Then
'This catches the case of two infinities of opposite sign, or
'one infinity and one finite number. Two infinities of opposite
'sign would otherwise have an infinite relative tolerance.
'Two infinities of the same sign are caught by the equality check
'above.
IsClose = False
Exit Function
Else
'Now do the regular computation on finite arguments. Here an
'infinite tolerance will always result in the function returning True,
'since an infinite difference will be <= to the infinite tolerance.
'NaN has already been filtered out in the equality checks earlier.
'Need to account for possible overflow here as both the difference
'and tolerances may be more than the range of a double and classified
'as infinite
Dim diff As Double
diff = diffWithOverflow(a, b)
If diff <= absTol Then
IsClose = True
Exit Function
ElseIf diff <= absMultiplyWithOverflow(relTol, b) Then
IsClose = True
Exit Function
ElseIf diff <= absMultiplyWithOverflow(relTol, a) Then
IsClose = True
Exit Function
End If
End If
End Function
'@Description("Returns |a*b| allowing for +inf in case of overflow, throws on all other errors")
Private Function absMultiplyWithOverflow(ByVal a As Double, ByVal b As Double) As Double
On Error GoTo checkErr
absMultiplyWithOverflow = Abs(a * b)
cleanExit:
Exit Function
checkErr:
If Err.Number = OVERFLOW_ERR Then Resume cleanExit
Err.Raise Err.Number
End Function
'@Description("Returns |b-a| allowing for +inf in case of overflow, throws on all other errors")
Private Function diffWithOverflow(ByVal a As Double, ByVal b As Double) As Double
On Error GoTo checkErr
diffWithOverflow = Abs(b - a)
cleanExit:
Exit Function
checkErr:
If Err.Number = OVERFLOW_ERR Then Resume cleanExit
Err.Raise Err.Number
End Function
'@Description "Checks if Number is IEEE754 +inf, won't raise an error"
Public Function IsInfinity(ByVal Number As Double) As Boolean
On Error Resume Next 'in case of NaN
IsInfinity = Abs(Number) = PosInf
On Error GoTo 0
End Function
'@Description("IEEE754 -inf")
Public Static Property Get NegInf() As Double
On Error Resume Next
NegInf = -1 / 0
On Error GoTo 0
End Property
'@Description("IEEE754 signaling NaN (sNaN)")
Public Static Property Get NaN() As Double
On Error Resume Next
NaN = 0 / 0
On Error GoTo 0
End Property
'@Description("IEEE754 +inf")
Public Static Property Get PosInf() As Double
On Error Resume Next
PosInf = 1 / 0
On Error GoTo 0
End Property
IsClose
函数可用于检查绝对差异:
assert(IsClose(0, 0.0001233, absTol:= 0.001)) 'same to 3 d.p.?
...或相对差异:
assert(IsClose(1234.5, 1234.6, relTol:= 0.0001)) '0.01% relative difference?
...但通常您指定两者,如果满足任一容差,则数字被认为是接近的。它对仅接近自身的 +-infinity 和几乎为零的 NaN 进行了特殊处理(请参阅 PEP 以获得完整的理由,或 my Code Review post 我希望对此代码提供反馈:)