【VBS】Val関数もどきを作ってみた

  • 0
    いいね
  • 0
    コメント

    はじめに

    既存アプリケーション(レガシーASP)でログ出力した際に処理時間の項目があるのですが、短い処理では指数表示になってしまいます。ログ解析で指数対応されてない場合、例えば3.12E-02は0.0312なんですが、3.12秒として扱われてしまう。
    改修作業として、指数表示にならないようにログ出力して欲しいとのことでした。

    ネットで検索したところ、Val関数を使えば出来るということで安易に考えていたのですが、いざVal関数を使ってみるとエラーとなりました。
    実はVB6やVBAならVal関数があったのですが、VBScriptにはVal関数が無かったのです。

    VBScriptの不足機能

    Visual Basic for Applications (VBA) と VBScript (VBS) は、どちらも基本的に Visual Basic の文法を踏襲しており、Visual Basic のステートメントや関数が使用可能です。
    VBScriptにはCvar、CVDate、Str、Val、StrConv などのデータ変換が無い
    代表的なものは以下の通りです (完全なリストについては「Visual Basic for Applications Features Not In VBScriptを参照)。

    • Collection オブジェクト
    • On Error Goto などのエラー制御
    • Cvar、CVDate、Str、Val、StrConv などのデータ変換
    • Debug.Print などのデバッグ用ステートメント
    • Declare による DLL の関数定義
    • Open、Print、Input、Write、Get、Put などのファイル操作
    • Like 演算子
    • イベント処理

    参照:Visual Basic for Applications を VBScript に置き換える方法 - Outlook 研究所

    Val関数もどきの仕様

    無いものは作成します。ただ全く同じにするのは難しいですし、簡易的過ぎても汎用性が無いため、それなりに真似ます。

    使用例

    説明 記述例 出力
    指定値をDouble型に変換 Val("1.23") 1.23

    動作検証

    下記表のVBS出力の項目が今回のVal関数もどきの値となります。
    VBA関数のVal関数と違い、数値の前の英字・記号や","があっても変換出来るようにしました。

    説明 記述例 VBA出力 VBS出力
    指数表記の数値(E-) Val("123E-2") 1.23 1.23
    指数表記の数値(E+) Val("123E+2") 12300 12300
    指数表記の数値(Eのみ) Val("123E2") 12300 12300
    指数表記の数値(E-0) Val("123E-02") 1.23 1.23
    指数表記の数値(E+0) Val("123E+02") 12300 12300
    指数表記の数値(E0のみ) Val("123E02") 12300 12300
    数値の前に文字列 Val("abc1.23") 0 1.23
    数値の間に文字列 Val("1.2abc3") 1.2 1.23
    数値の後に文字列 Val("1.23abc") 1.23 1.23
    先頭が点の文字列 Val(".123") 0.123 0.123
    末尾が点の文字列 Val("123.") 123 123
    先頭が複数点の文字列 Val("...123") 0 0.123
    末尾が複数点の文字列 Val("1.23...") 1.23 1.23
    数値以外の文字列のみ Val("abc") 0 0
    全角数値の文字列のみ Val("12.34") 0 0
    「,」区切りの数値 Val("1,234,567") 1 1234567
    8進数の数値 Val("&O123") 83 83
    16進数の数値(整数型) Val("&H123") 291 291
    16進数の数値(負数) Val("&HFFE0") -32 -32
    16進数の数値(長整数型) Val("&H123abc") 1194684 1194684
    スペースを含んだ文字列 Val(" 123.45 円") 123.45 123.45

    ※値が大きい指数有り場合、指数が付いたままになります。

    説明 記述例 VBA出力 VBS出力
    Double型の最小値近く(-1.79769313486231E+308) Val("-1.79769313486231E+308") -1.79769313486231E+308 -1.79769313486231E+308
    Double型の最大値近く( 1.79769313486231E+308) Val("1.79769313486231E+308") 1.79769313486231E+308 1.79769313486231E+308

    ソースリスト

    Function Val(value)
        Dim colMatches, match, submatch, sign, reg, exp, i, j, st
    
        Val = 0
    
        'COMの正規表現を使用すると型が自動変換される
        Set reg = CreateObject("VBScript.RegExp")
        reg.Pattern = "([-0-9.]+)(E[+-]?[0-9]+)|([-0-9.]+)|(&H[0-9A-F]+)|(&O[0-9]+)"
        reg.IgnoreCase = True
        reg.Global = True
        Set colMatches = reg.Execute(value)
    
        For Each match In colMatches
            For i = 0 To match.SubMatches.Count - 1
                submatch = UCase(match.SubMatches(i))
    
                If Instr(submatch, "E") = 1 Then
                    '連続Eを排除
                    submatch = Replace(submatch, "E", "")
                    sign = Mid(submatch, 1,1)
                    st = 1
                    If sign = "-" OR sign = "+" Then st = 2
                    If sign = "" Then submatch = submatch + "0"
                    exp = Mid(submatch, st)
                    If exp <> "" Then
                        '指数あり
                        exp = 10 ^ CInt(exp)
                        If sign = "-" Then
                            Val = Val / exp
                        Else
                            Val = Val * exp
                        End If
                    End If
                Else
                    '指数なし
                    Val = Val & submatch
                End If
    
                '小数0.以外の先頭0を消去する
                If Instr(Val, "0") = 1 And Instr(Val, "0.") <> 1 then
                    Val = Mid(Val, 2)
                End If
            Next
        Next
    
        '16進数と8進数対応
        If Instr(value, "&") > 0 then
            Val = CInt(Val)
        End If
    
        '末尾"."を除去
        For i = Len(Val) To 0 Step -1
            If Right(Val, 1) = "." Then
                Val = Left(Val, i-1)
            Else
                Exit For
            End If 
        Next
    
        '先頭"."は0を頭に追加
        If Instr(Val, ".") = 1 then
            Val = "0." & Replace(Val, ".", "") 
        End If 
    
        '変換対象外は0とする
        If Val = "" OR IsNumeric(Val) = False Then Val = 0
    
        Set reg = Nothing
    End Function
    

    ライセンスっぽいこと

    コード改変や配布は自由です。
    このツールによる義務/責任を何ら負いません。

    最後に

    最初に数値型の指数ではCOMの正規表現オブジェクトを使用しただけで型が自動変換されたのであっさり出来ると思われたのですが、2重引用符を付けて文字型にしたら自動変換が正常な値を返さなくなり、結局がっちり組むことになってしまいました。

    参照

    この投稿は Visual Basic Advent Calendar 20168日目の記事です。