15

我无法在 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

你知道我怎样才能消除双精度类型的尾随不精确吗?

4

9 回答 9

15

您无法比较浮点值是否相等。有关如何处理内在错误的讨论,请参阅这篇关于“比较浮点数”的文章。

除非您事先确定浮点数的绝对范围是多少,否则它不像与恒定误差范围进行比较那么简单。

于 2008-10-24T22:12:46.340 回答
7

如果你要这样做......

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  

另请参阅此处以获取其他 Microsoft 参考

于 2011-04-07T06:14:21.673 回答
5

在平等方面比较双打是不明智的。

一些十进制值映射到几个浮点表示。所以一个 0.6 并不总是等于另一个 0.6。

如果我们从另一个中减去一个,我们可能会得到类似 0.00000000051 的结果。

我们现在可以将相等定义为具有小于某个误差范围的差异。

于 2008-10-24T22:09:40.387 回答
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)
于 2014-06-04T15:14:25.800 回答
2

正如已经指出的那样,许多十进制数不能精确地表示为传统的浮点类型。根据您的问题空间的性质,您最好使用 Decimal VBA 类型,它可以以完美的精度表示十进制数(以 10 为基数),直到某个小数点。这通常用于表示货币,例如通常需要 2 位小数精度的情况。

Dim a as Decimal
Dim b as Decimal
a = 0.15
b = 0.01
于 2008-10-24T23:18:39.170 回答
1

Currency 数据类型可能是一个不错的选择。它以固定的四位数精度处理相对较大的数字。

于 2008-10-27T21:36:58.350 回答
0

轮班??不确定这是否会回答所有情况,但我在比较 VBA 中的舍入双精度值时遇到了问题。当我比较舍入后看起来相同的数字时,VBA 会在 if-then 比较语句中触发 false。我的解决方法是运行两次转换,首先是双精度字符串,然后是双精度字符串,然后进行比较。

模拟示例 我没有记录导致本文中提到的错误的确切数字,并且我示例中的金额当前不会触发问题,旨在代表问题的类型。

 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
于 2019-06-06T17:53:45.610 回答
0

迟到的答案,但我很惊讶没有发布解决方案来解决(当前)接受的答案中链接的文章中概述的问题,即:

  • 四舍五入检查绝对容差的相等性(例如,如果四舍五入为 4d.p.,则为 0.0001 个单位),这在比较多个数量级的不同值时是垃圾(所以不仅仅是与 0 比较)
  • 当前答案中没有提到与同时被比较的数字之一成比例的相对容差,但在非零比较中表现良好(但是在与零比较时会很糟糕,因为当时比例会爆炸)。

为了解决这个问题,我从 Python 中获得了灵感:PEP 485 -- A Function for testingapproximateequality to implement the following (in a standard module):

代码

'@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 以获得完整的理由,或我的代码审查帖子,我希望对此代码提供反馈:)

于 2021-07-07T18:29:23.473 回答
-1

如果可能,请尝试使用 Single 值。转换为 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 
于 2020-05-23T12:14:03.067 回答