VBの IsNumeric の限界値から正体を探る

# VBの IsNumeric の限界値から正体を探る

## 結論

Visual Basicの`IsNumeric`が`True`と判定する最大/最小の整数値↓(309桁)

```cs
digits: 309
upper integer limit: 17976931348623158099999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
digits: 309
lower integer limit: -17976931348623158099999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
```

`Double`型との共通点がみられた。

`IsNumeric`が`True`だったからって、そのまま数値型に変換してはいけない。

## 経緯

仕事でVB6からC#への書き換え中、テキストボックスへの入力値を`IsNumeric`のみで判定しSingle型に変換するコードを発見した。※  
これ上限/下限判定どうなっとんじゃい、ということでググったが、どこにも`IsNumeric`の限界が書いてなかった。  
せっかくなので検証コードを書き、`IsNumeric`の限界を調べてみた。  

※当然、実質的には`Single`型の範囲がそのまま入力値を制限する。でも判定用メソッドでちゃんと弾いてほしいよね。

## 検証

2024/10/04現在、VB6の開発環境を構築することは難しい。  
しかし、.Net Frameworkの`Microsoft.VisualBasic`名前空間にVBの互換メソッドが詰まっている。※  
私自身がC#に慣れているのもあるので、今回はこちらをつかって検証した。
フレームワークは`.NET Framework 4.6.1`。

※プロジェクトに`Microsoft.VisualBasic`アセンブリへの参照を追加しないと使えないので注意。

以下、検証用コード。

```cs
using System;
using Microsoft.VisualBasic;

public class Program
{
    public static void Main()
    {
        Console.WriteLine("upper integer limit: " + IntegerLimit(true));
        Console.WriteLine("lower integer limit: " + IntegerLimit(false));
        Console.ReadLine();
    }

    private static string IntegerLimit(bool isPositive)
    {
        string confirmed = isPositive ? "1" : "-1"; // 確定した位
        int changing = 0;   // いじる位
        int zeros = 0;      // ゼロの桁数

        // 何桁までいけるのか調べる
        while (true)
        {
            string text = confirmed + changing.ToString() + new string('0', zeros);
            bool isNumeric = Information.IsNumeric(text);

            if (isNumeric)
            {
                zeros++;
            }
            else
            {
                Console.WriteLine("digits: " + (zeros + 1));
                zeros--;
                break;
            }
        }

        // 具体的な数字を上の位から順に調べる
        while (zeros > 0)
        {
            string text = confirmed + changing.ToString() + new string('0', zeros);
            bool isNumeric = Information.IsNumeric(text);

            if (isNumeric)
            {
                changing++;
                if (changing > 9)
                {
                    Confirm();
                }
            }
            else
            {
                Confirm();
            }

            // 一桁確定させる内部メソッド
            void Confirm()
            {
                changing--;
                confirmed = confirmed + changing.ToString();
                zeros--;
                changing = 0;
            }
        }

        return confirmed;
    }
}
```

結果：

```cs
digits: 309
upper integer limit: 17976931348623158099999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
digits: 309
lower integer limit: -17976931348623158099999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
```

## 考察

まず、正負どちらも数値部分の桁数は変わらず309桁だった。
また、左から数えて比較的早い段階(18桁)で数値は変化しなくなり、あとは9が埋めることになった。

ところで、VBの`Double`型の範囲はこんな感じだ。

> __-1.79769313486231570 E + 308__ から -4.94065645841246544 E-324 (負の値の場合) および 4.94065645841246544 E-324 から __1.79769313486231570 E + 308__ (正の値の場合) までの値の範囲の、符号付き IEEE 64 ビット (8 バイト) の倍精度浮動小数点数を保持します。 倍精度の数値は、実数の概数を格納します。

https://learn.microsoft.com/ja-jp/dotnet/visual-basic/language-reference/data-types/double-data-type

https://nj-clucker.com/type-of-visual-studio-6/

`1797693134862315`まで、`IsNumeric`の限界値と一致している。おまけに、桁数も一致している（調査結果の方が1多いのは、`Double`の方では数えられていない一番左の1も数えているから）

このことから、IsNumericでは内部では`Double`型がかかわる形で変換しているのでは？と推測できる。  
しかし`1797693134862315`以降はずれが生じ、`IsNumeric`の方が絶対値が若干大きい範囲まで数値と判定しているのが謎。  

そもそも、`IsNumeric`はかなり対応の幅が広い（悪い意味で）。  
そのままでは数値型にキャストできない文字列も`True`を返してしまうそうだ。

https://nam-it.blog.jp/archives/7602639.html

いずれにせよ上記のURLにもあるように、これだけを使って判定するのはよした方がよさそう。  
~~今VBで開発してる人いる？~~ __VBAの人も気を付けよう。__

## おわり

気づきまではいったが、知識不足でそこからの考察が片手落ちになってしまった。  
時間があれば検証コードを書き直して、小数も含めて限界値を測ってみたい。