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

# はじめに
既存アプリケーション(レガシー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](https://msdn.microsoft.com/en-us/library/30593abb%28v=vs.84%29.aspx?tduid=%286aa1b326fa8d4f62b21e5407acf93183%29%28256380%29%282459594%29%28TnL5HPStwNw-3FPZxo1ioGlaQ8YLCTJVaw%29%28%29&f=255&MSPPError=-2147217396)を参照)。

  * 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 研究所](https://outlooklab.wordpress.com/2014/03/08/visual-basic-for-applications-%E3%82%92-vbscript-%E3%81%AB%E7%BD%AE%E3%81%8D%E6%8F%9B%E3%81%88%E3%82%8B%E6%96%B9%E6%B3%95/)

# 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("１２．３４")|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|

# ソースリスト

```vb
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重引用符を付けて文字型にしたら自動変換が正常な値を返さなくなり、結局がっちり組むことになってしまいました。


# 参照
* [isnumeric関数について - VB初心者友の会](http://www.gizcollabo.jp/vbtomo/log/archive/vbqanda_9_6.html)
* [指定した文字列に含まれる値を取得する (Val) - C#とVB.NETの入門サイト](http://jeanne.wankuma.com/tips/vb.net/validation/val.html)
* [VB6・VBA関数メモ：Val関数 - フィロの村note](http://note.phyllo.net/?eid=1106110)
* [Denis St-Pierre's Val( ) function](http://www.robvanderwoude.com/vbstech_data_string.php)