VBA 정밀도 문제에서 두 배 비교
엑셀 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
이중 타입의 후행 정밀도를 어떻게 제거할 수 있는지 알고 계십니까?
부동 소수점 값은 동일성에 대해 비교할 수 없습니다.고유 오류를 처리하는 방법에 대한 자세한 내용은 "부동점 번호 비교"에 대한 이 문서를 참조하십시오.
플로트의 절대 범위를 사전에 확실히 알지 못하는 한 일정한 오차 한계와 비교하는 것만큼 간단하지 않습니다.
만약 당신이 이것을 할 것이라면...
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에 대한 추가 참조는 여기를 참조하십시오.
평등에 대해 복식을 비교하는 것은 결코 현명하지 않습니다.
일부 소수점 값은 여러 부동 소수점 표현에 매핑됩니다.따라서 하나의 0.6이 항상 다른 0.6과 같지는 않습니다.
우리가 다른 것에서 하나를 빼면, 아마도 0.000000051과 같은 것을 얻을 수 있을 것입니다.
이제 우리는 평등을 특정 오차 한계보다 작은 차이를 갖는 것으로 정의할 수 있습니다.
제가 작성한 간단한 기능은 다음과 같습니다.
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)
답변이 늦었지만, (현재) 수락된 답변에 링크된 기사에 요약된 우려 사항을 해결하는 솔루션이 아직 게시되지 않은 것은 놀라운 일입니다.
- 반올림은 절대 공차(예: 4d.p.로 반올림한 경우 0.0001 단위)로 동일성을 확인합니다. 이는 여러 크기 순서에서 서로 다른 값을 비교할 때 쓰레기입니다(따라서 0과만 비교하는 것이 아님).
- 한편 비교 중인 숫자 중 하나로 확장되는 상대 공차는 현재 답변에서 언급되지 않았지만 0이 아닌 비교에서 잘 수행됩니다(그러나 그 무렵 확장이 증가함에 따라 0과 비교하는 데는 좋지 않을 것입니다).
이 문제를 해결하기 위해, 저는 Python: PEP 485 -- 다음을 구현하기 위해 대략적인 동등성을 테스트하는 함수에서 영감을 얻었습니다(표준 모듈에서).
코드
'@NoIndent: Don't want to lose our description annotations
'@Folder("Tests.Utils")
Option Explicit
Option Private Module
'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, e.g. abs(a - b)/(a OR b) < relTol
' absTol: double = 0.0
' maximum difference for being considered "close", regardless of the
' magnitude of the input values, e.g. abs(a - b) < absTol
'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
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
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
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.
On Error Resume Next 'This is to suppress overflow errors as we deal with infinity.
Dim diff As Double: diff = Abs(b - a)
If diff <= absTol Then
IsClose = True
ElseIf diff <= CDbl(Abs(relTol * b)) Then
IsClose = True
ElseIf diff <= CDbl(Abs(relTol * a)) Then
IsClose = True
End If
On Error GoTo 0
End If
End Function
'@Description "Checks if Number is IEEE754 +/- inf, won't raise an error"
Public 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 Property Get NegInf() As Double
On Error Resume Next
NegInf = -1 / 0
On Error GoTo 0
End Property
'@Description "IEEE754 +inf"
Public Property Get PosInf() As Double
On Error Resume Next
PosInf = 1 / 0
On Error GoTo 0
End Property
'@Description "IEEE754 signaling NaN (sNaN)"
Public Property Get NaN() As Double
On Error Resume Next
NaN = 0 / 0
On Error GoTo 0
End Property
'@Description "IEEE754 quiet NaN (qNaN)"
Public Property Get QNaN() As Double
QNaN = -NaN
End Property
Code Review에 대한 Cristian Bus의 훌륭한 피드백을 반영하도록 업데이트됨
예
그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?
그러나 일반적으로 두 값을 모두 지정하고 두 값 중 하나의 공차가 충족되면 숫자가 근접한 값으로 간주됩니다.자신에게만 가까운 +-무한과 거의 없는 NaN을 특별하게 처리합니다(완전한 정당성은 PEP 또는 이 코드에 대한 피드백을 좋아하는 내 코드 리뷰 게시물 참조).
지적했듯이, 많은 십진수는 전통적인 부동 소수점 유형으로 정확하게 표현될 수 없습니다.문제 공간의 특성에 따라 특정 소수점까지 십진수(기본값 10)를 정확하게 나타낼 수 있는 십진수 VBA 유형을 사용하는 것이 더 나을 수 있습니다.이것은 종종 두 자리 소수점 정밀도가 종종 필요한 예를 들어 돈을 표현하기 위해 수행됩니다.
Dim a as Decimal
Dim b as Decimal
a = 0.15
b = 0.01
통화 데이터 유형이 좋은 대안이 될 수 있습니다.이것은 고정된 4자리 정밀도로 비교적 큰 숫자를 처리합니다.
사용자의 상황과 데이터에 따라 그리고 기본적으로 표시되는 정확도 수준에 만족하는 경우 매우 간단한 코딩 솔루션으로 숫자의 문자열 변환을 비교해 볼 수 있습니다.
if cstr(a) = cstr(b)
여기에는 기본적으로 표시되는 정도의 정밀도가 포함되며, 이는 일반적으로 숫자가 같다고 간주하기에 충분합니다.
이는 매우 큰 데이터 세트에서는 비효율적이지만 VBA 어레이에 데이터를 저장한 후 동일하지만 일치하지 않는 가져온 데이터를 조정할 때 유용했습니다.
우회적으로 일을?이것이 모든 시나리오에 답할 수 있을지 확신할 수 없지만, 저는 VBA에서 반올림된 이중 값을 비교하는 문제에 부딪혔습니다.반올림 후 동일한 것으로 보이는 숫자와 비교했을 때, VBA는 if-then compare 문에서 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
가능한 경우 단일 값을 사용합니다.이중 값으로 변환하면 랜덤 오류가 발생합니다.
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
언급URL : https://stackoverflow.com/questions/235409/compare-double-in-vba-precision-problem
'codememo' 카테고리의 다른 글
| 리소스 u'tokenizers/punkt/english.pickle'을(를) 찾을 수 없습니다. (0) | 2023.09.05 |
|---|---|
| 이렇게 생긴 XMLHttpRequest에 데이터 본문을 보내는 방법은 무엇입니까? (0) | 2023.09.05 |
| 알림 클릭에서 활동으로 매개 변수를 보내는 방법은 무엇입니까? (0) | 2023.09.05 |
| Mariadb 할당된 메모리 구성 (0) | 2023.09.05 |
| 크롬/오페라에서 CSS3 둥근 모서리가 오버플로를 숨기도록 만드는 방법 (0) | 2023.09.05 |