对于不同的区域设置,IsNumeric 在 excel/vba 中给出了意想不到的结果

IsNumeric gives unexpected results in excel/vba for different regional settings

我的印象是 IsNumeric(MyString) 会显示 Val(MyString) 是否预计会失败。根据区域设置,我发现了意想不到的差异。

示例1,瑞典区域设置(使用,作为小数点分隔符):

示例2,爱沙尼亚区域设置(也使用,作为小数点分隔符):

我将字符串转换为数字的典型通用代码是:

Function ConvertToNumber(MyNumber as String) as Double 
    If IsNumeric(MyNumber) then
        ConvertToNumber = Val(Replace(MyNumber, ",", "."))
    Else
        MsgBox "Invalid format!"
    End If
End Function

但这在爱沙尼亚区域设置中意外失败。知道这是否是预期的行为吗?微软对 IsNumber 正在做什么的解释也有点含糊。你建议改用什么? If Val(MyNumber & "1")<>0?对于某些特殊情况,例如 0E+0,这将失败。还可以考虑从以下位置捕获任何错误:

CDbl(Replace(MyString, ".", Application.International(xlDecimalSeparator))

我可以使用正则表达式,但必须有更好的方法。

我将不胜感激。

/乔纳斯

不幸的是, 我不熟悉绕过那些特定区域设置以使用内置函数 IsNumeric()。但是幸运的是,我可以帮助您在正则表达式的帮助下创建您自己的自定义函数。

您可以使用以下功能。将它添加到标准代码模块后,只需使用 IsNumber("1.1") 而不是 IsNumeric("1.1").

Function IsNumber(ByVal TestString As Variant) As Boolean

    With CreateObject("VBScript.RegExp")
    
        .Pattern = "^(?:\d+[.,]?\d*|\d*[.,]\d+)$"
        IsNumber = .test(TestString)
        
    End With
        
End Function

当然,如果其他人提出更直接的方法,我建议使用该方法。但是,这应该对您有用。


让我们快速分解一下模式 ^(?:\d+[.,]?\d*|\d*[.,]\d+)$。此模式有一个 OR 运算符,即 | 符号,因此我们将单独查看它们。

  • ^ 断言这是字符串的开头。
  • (?:...) 是非捕获组。这只是为了我们不必为每个 OR | 语句添加字符串开头 ^ 和字符串结尾 $ 锚点。
  • \d+ 匹配任何数字字符 \d,一次或多次 +,然后紧接着是
  • [.,]?是一个字符组,它将匹配该组中的任何字符,一个?(使其成为可选),然后紧跟
  • \d*,它将匹配任何数字 \d更多*(本质上是可选的,但可以有无限个字符)
  • 最后,我们有字符串锚点的结尾 $

OR 语句的第二部分本质上是相同的,但我将第一个 \d 设为可选,以允许诸如 .1 之类的事情(注意 [=34 前面没有数字) =]) 仍然匹配为数字。


综上所述,现在您可以将代码更新为:

Function ConvertToNumber(MyNumber as String) as Double 
    If IsNumber(MyNumber) then
        'Your code
    Else
        'Your code
    End If
End Function

尽管如果是我的代码,我会在发送到您的转换函数之前检查数值:

If IsNumber(MyNumber) Then
    Debug.Print ConvertToNumber(MyNumber)
Else
    'Handle it here
End If

虽然这并没有太大的区别,但这对我个人来说更重要。

问题是使用 Val function

Returns the numbers contained in a string as a numeric value of appropriate type.

所以它不是您所期望的,因为它不会将字符串转换为数字,而是提取字符串中包含的数字直到第一个不是空格的非数字字符。

这意味着

Val("    1615 198th Street N.E.")

将 return 1615198 作为两倍。

所以 Val 在所有本地化中解释为小数的始终是 .。这意味着它将始终将 Val("1.1") 视为数字,但在 Val("1,1") 中,逗号是第一个非数字、非空白字符,因此它停在那里并且 returns 1只有.

您正在寻找的是 CDbl,它实际上使用您系统的小数分隔符 将字符串 转换为数字。

Option Explicit

Function ConvertToNumber(ByVal MyNumber As String) As Double
    Dim RegionalNumber As String
    RegionalNumber = MyNumber
    
    ' if you want the user to be able to enter dots as well as commas
    ' make sure all dots and commas are converted to the decimal seperator of your system
    ' otherwise go without the conversion
    RegionalNumber = Replace$(RegionalNumber, ".", Application.DecimalSeparator)
    RegionalNumber = Replace$(RegionalNumber, ",", Application.DecimalSeparator)
    
    If IsNumeric(RegionalNumber) Then
        ConvertToNumber = CDbl(RegionalNumber)
    Else
        MsgBox "Invalid format!"
    End If
End Function

如果你用

测试这个
Sub test() 
    Debug.Print ConvertToNumber("1.1")
    Debug.Print ConvertToNumber("1,1")
End Sub

在逗号作为小数点分隔符的系统中,它应该两次 return 1,1 作为数字。


解释为什么你的测试 return 编辑了他们 return 编辑的结果

示例 1,瑞典区域设置(使用“,”作为小数点分隔符):

  • IsNumeric("1.1")=> 正确
    因为 . 被认为是日期分隔符所以它是一个有效的数字
  • Val("1.1")=> 1.1
    因为 val 始终将点视为小数点分隔符
  • IsNumeric("1,1")=> 正确
    因为 , 被认为是 十进制 分隔符
  • Val("1,1")=> 1 因为 val 总是将逗号视为非数字字符

示例2,爱沙尼亚语区域设置(也使用','作为小数点分隔符):

  • IsNumeric("1.1")=>
    因为爱沙尼亚语中的点 不是 被视为日期分隔符,所以这不是有效数字
  • Val("1.1")=> 1.1
    因为 val 始终将点视为小数点分隔符
  • IsNumeric("1,1")=> 正确
    因为 , 被认为是小数分隔符
  • Val("1,1")=> 1 因为 val 总是将逗号视为非数字字符

请注意 IsNumeric 接受千位分隔符、日期分隔符和小数点分隔符以及货币符号。