【问题标题】:Comparing doubles returns false比较双打返回 false
【发布时间】:2017-12-14 13:06:35
【问题描述】:

我的数据库中有三个数字,想在 if 语句中比较它们。

我有一个简单的 convert 函数,它只返回双精度数。

Public Function RetDbl(ByVal obj As Variant) As Double
    On Error Resume Next
    RetDbl = val(Replace(Nz(obj, 0), ",", "."))
End Function

声明是

If RetDbl(rs.value("NumA")) + RetDbl(rs.value("NumB")) <> (RetDbl(rs.value("NumC")) * 1000) Then
    '[... do some code ...]
End If

使用RetDbl(rs.value("NumA")) = 0.33RetDbl(rs.value("NumB") = 0.5RetDbl(rs.value("NumC")) = 0.00083

这总是返回 false

我也试过了:

在直接字段 (STRG + G) 中:?cdbl(0.33) + cdbl(0.50) = cdbl(0.83) 返回 false。当我省略最后一部分时,它返回0.83

如何比较这些数字?

【问题讨论】:

  • 浮点数不是精确值。因此,将它们与精确值进行比较可能会失败(如您所见)。最好检查|a - b| &lt; epsilon是否有一些适当的epsilon
  • 我想我应该用这个。但到目前为止,它已经工作了几天。即使我输入?0.33 + 0.50 = 0.83i 只会得到错误。而且我从来没有告诉程序这些是双打。
  • @Ronald 是正确的。 0.33 + 0.5 = 0.8300000000000001。您还可以尝试舍入或截断以仅比较浮点数的重要部分。请参阅this website,了解有关浮点数在计算机程序中的行为方式的大量信息。
  • @Ronald 请注意,如果您要创建一个函数来进行比较,建议使用相对 epsilon
  • @ErikvonAsmuth 是的,我知道。这就是我写“一些适当的ε”的原因。通常你有 8-12 位的精度(但这可能会更糟,取决于历史;如果它是数值不稳定算法的结果,你可以得到任何结果),因此与 10^-8 * a 比较可能是一个合适的值。由于我对号码的“历史”一无所知,所以我对发布号码有点谨慎。

标签: vba ms-access


【解决方案1】:

比较浮点数很难。就在昨天,我发了this question

我的解决方案:

Public Function DblSafeCompare(ByVal Value1 As Variant, ByVal Value2 As Variant) As Boolean
    'Compares two variants, dates and floats are compared at high accuracy
    Const AccuracyLevel As Double = 0.00000001
    'We accept an error of 0.000001% of the value
    Const AccuracyLevelSingle As Single = 0.0001
    'We accept an error of 0.0001 on singles
    If VarType(Value1) <> VarType(Value2) Then Exit Function
    Select Case VarType(Value1)
        Case vbSingle
            DblSafeCompare = Abs(Value1 - Value2) <= (AccuracyLevelSingle * Abs(Value1))
        Case vbDouble
            DblSafeCompare = Abs(Value1 - Value2) <= (AccuracyLevel * Abs(Value1))
        Case vbDate
            DblSafeCompare = Abs(CDbl(Value1) - CDbl(Value2)) <= (AccuracyLevel * Abs(CDbl(Value1)))
        Case vbNull
            DblSafeCompare = True
        Case Else
            DblSafeCompare = Value1 = Value2
    End Select
End Function

请注意,AccuracyLevel (epsilon) 可以设置为较小的值,我对单打和双打使用相同的值,但它对我的目的来说效果很好。

我使用的是相对 epsilon,但将它与第一个值相乘,而不是最大值,因为如果存在显着差异,则比较无论如何都会失败。

请注意,我使用的是 &lt;= 而不是 &lt;,因为否则 DblSafeCompare(cdbl(0) ,cdbl(0)) 会失败

请注意,此函数检查类型是否相等,因此将整数与长整数、双精度数与单数等进行比较都失败了。但是,将 Null 与 Null 传递进行比较。

实现它:

?DblSafeCompare(cdbl(0.33) + cdbl(0.50) ,cdbl(0.83))
?DblSafeCompare(cdbl(0.331) + cdbl(0.50) ,cdbl(0.83))

【讨论】:

  • 感谢您提供代码。从来没有想过它必须那么复杂。但现在它就像一个魅力!
  • 请注意,我共享的代码是针对我的用例的。因此DblSafeCompare(Null, Null)TrueDblSafeCompare(1, CDbl(1))False
【解决方案2】:

比较浮点数确实是一个问题,如果您尝试在不了解浮点数的性质的情况下进行比较。

这是一篇关于它的好文章 - https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.htmlHow dangerous is it to compare floating point values?

一般来说,这个问题是如此之大,以至于像 C# 这样的一些语言已经开发了一个名为 Decimal 的特定类,它可以让比较像非程序员所期望的那样运行。 Decimal info。在VBA 中,类似的类是Currency。因此

CCur(0.33) + CCur(0.50) = CCur(0.83)

返回True。 VBA 支持函数CDec,它将双精度数转换为十进制数,但不支持类Decimal。因此:

CDec(0.33) + CDec(0.50) = CDec(0.83)

也会返回True。并且比Currency 具有更好的准确性。 CDec documentation.

【讨论】:

  • 对于分数,在使用现代 VBA 应用程序时(找不到引入年份),我建议使用 Decimal 数据类型,而不是 Currency 数据类型(CDec 不是 @ 987654340@)。十进制是一种更大的数据类型,它支持更精确的值。货币限制为 4 位小数。小数是具有 29 位有效数字的非浮点数。
  • @ErikvonAsmuth - 不幸的是,VBA 不支持 Decimal。但是,CDec 函数确实是一个不错的选择。
  • 嗯,不支持可能是真的。您不能声明小数。但是您可以在变体中保存一个。请参阅以下示例:Public Sub TestDec(): Dim dec1 As Variant: Dim dec2 As Variant: dec1 = CDec(0.3301111) + CDec(0.5): dec2 = CDec(0.8301111) : Debug.Print dec1 = dec2 : End Sub 这将在 office 2010 中打印True。您还可以在 Microsoft Access 中存储小数。抱歉格式错误
  • @ErikvonAsmuth - 是的。我想编辑我的评论,但已经过了 5 分钟 :)
猜你喜欢
  • 1970-01-01
  • 2016-02-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-15
相关资源
最近更新 更多