C# の数値型

# はじめに

[C# の数値型](https://learn.microsoft.com/ja-jp/dotnet/standard/numerics)は色々種類があります。

- クラス名と短縮名がある（`int` 等の組み込み型）
- 読み取り専用の値型
- 実行環境に依存せず同じサイズ（ポインタ型 `nint` `nuint` を除く）

整数型

|型名|短縮名|`sizeof()`|10 進数桁数|備考|
|---|---|---:|---:|---|
|`System.Byte`|`byte`|1|3|符号なし|
|`System.SByte`|`sbyte`|1|3||
|`System.Int16`|`short`|2|5||
|`System.UInt16`|`ushort`|2|5|符号なし|
|`System.Int32`|`int`|4|10||
|`System.UInt32`|`uint`|4|10|符号なし|
|`System.Int64`|`long`|8|19||
|`System.UInt64`|`ulong`|8|20|符号なし|
|[**`System.Int128`**](https://learn.microsoft.com/ja-jp/dotnet/api/system.int128?view=net-9.0)|-|16|39|.NET 7 で導入|
|[**`System.UInt128`**](https://learn.microsoft.com/ja-jp/dotnet/api/system.uint128?view=net-9.0)|-|16|39|.NET 7 で導入、符号なし|
|`System.IntPtr`|`nint`|4 または 8|-|ポインタ、実行環境依存|
|`System.UIntPtr`|`nuint`|4 または 8|-|ポインタ、実行環境依存、符号なし|

浮動小数点数型
|型名|短縮名|`sizeof()`|10 進数桁数|備考|
|---|---|---:|---:|---|
|[**`System.Half`**](https://learn.microsoft.com/ja-jp/dotnet/api/system.half?view=net-9.0)|-|2|-|.NET 5 で導入|
|`System.Single`|`float`|4|-||
|`System.Double`|`double`|8|-||
|`System.Decimal`|`decimal`|16|29||

128bit 整数とかあったんですねえ・・・ 初めて知りました。

## テストコード

<details>
<summary>テストコード</summary>

```cs
using Xunit;

public class __NumericsTest
{
    [Fact]
    public void SizeOf()
    {
        Assert.Equal(1, sizeof(byte));
        Assert.Equal(1, sizeof(sbyte));

        Assert.Equal(2, sizeof(short));
        Assert.Equal(2, sizeof(ushort));

        Assert.Equal(4, sizeof(int));
        Assert.Equal(4, sizeof(uint));

        Assert.Equal(8, sizeof(long));
        Assert.Equal(8, sizeof(ulong));

        unsafe
        {
            Assert.Equal(16, sizeof(Int128));
            Assert.Equal(16, sizeof(UInt128));
        }

        unsafe
        {
            Assert.Equal(2, sizeof(Half));
        }
        Assert.Equal(4, sizeof(float));
        Assert.Equal(8, sizeof(double));
        Assert.Equal(16, sizeof(decimal));

        // 実行環境依存
        // unsafe
        // {
        //     Assert.Equal(8, sizeof(nint));
        //     Assert.Equal(8, sizeof(nuint));
        // }
    }

    [Fact]
    void NumberOfDecimalDigits()
    {
        int GetNumberOfDecimalDigits(Int128 maxValue)
        {
            var result = 1;
            for (; result < 100; result++)
            {
                maxValue /= 10;
                if (maxValue == 0)
                    break;
            }

            return result;
        }

        int GetNumberOfDecimalDigitsU(UInt128 maxValue)
        {
            var result = 1;
            for (; result < 100; result++)
            {
                maxValue /= 10;
                if (maxValue == 0)
                    break;
            }

            return result;
        }

        Assert.Equal(3, GetNumberOfDecimalDigits(byte.MaxValue));
        Assert.Equal(3, GetNumberOfDecimalDigits(sbyte.MaxValue));

        Assert.Equal(5, GetNumberOfDecimalDigits(short.MaxValue));
        Assert.Equal(5, GetNumberOfDecimalDigits(ushort.MaxValue));

        Assert.Equal(10, GetNumberOfDecimalDigits(int.MaxValue));
        Assert.Equal(10, GetNumberOfDecimalDigits(uint.MaxValue));

        Assert.Equal(19, GetNumberOfDecimalDigits(long.MaxValue));
        Assert.Equal(20, GetNumberOfDecimalDigits(ulong.MaxValue));

        Assert.Equal(39, GetNumberOfDecimalDigits(Int128.MaxValue));
        Assert.Equal(39, GetNumberOfDecimalDigitsU(UInt128.MaxValue));

        Assert.Equal(5, GetNumberOfDecimalDigits((Int128)Half.MaxValue));
        Assert.Equal(39, GetNumberOfDecimalDigits((Int128)float.MaxValue));
        Assert.Equal(39, GetNumberOfDecimalDigits((Int128)double.MaxValue));
        Assert.Equal(29, GetNumberOfDecimalDigits((Int128)decimal.MaxValue));
    }

    static void Sqrt(Performance p)
    {
        p.AddTest("Math", () =>
        {
            var sum = 0.0;
            for (int i = 0; i < 1000000; i++)
                sum += Math.Sqrt(i);
        });

        p.AddTest("MathF", () =>
        {
            var sum = 0.0f;
            for (int i = 0; i < 1000000; i++)
                sum += MathF.Sqrt(i);
        });
    }
}
```

</details>

## その他

[System.Numerics 名前空間](https://learn.microsoft.com/ja-jp/dotnet/api/system.numerics?view=net-9.0)に様々なクラスが用意されています。以下は一部を抜粋

- [BigInteger](https://learn.microsoft.com/ja-jp/dotnet/api/system.numerics.biginteger?view=net-9.0): 任意の大きさを持つ符号付き整数を表します。
- [Complex](https://learn.microsoft.com/ja-jp/dotnet/api/system.numerics.complex?view=net-9.0): 複素数を表します。
- [Vector2](https://learn.microsoft.com/ja-jp/dotnet/api/system.numerics.vector2?view=net-9.0): 2 つの単精度浮動小数点値を持つベクトルを表します。

[`System.Math`](https://learn.microsoft.com/ja-jp/dotnet/api/system.math?view=net-9.0) は数学系のメソッドが用意されています（`Sqrt()` `Cos()`）。  
[`System.MathF`](https://learn.microsoft.com/ja-jp/dotnet/api/system.mathf?view=net-9.0) はその `float` バージョンで、数値の精度が低くても良い場合はこちらを検討できます。

## おわりに

.NET Framework 時代にはなかった型がちらほら追加されています。マイナーな数値型を使う機会は限定されそうですが、知ってるとスマートに解決できるケースもありそうです（あった）。