C#の基本的な数値型同士の計算って何型になるんだっけ？

- intとlongで計算したら結果はlongになる
- intとdoubleで計算したら結果はdoubleになる

なんて話をよく聞くと思いますが、数値でも沢山の型があるC#というか.NETというかでどれとどれを計算したらどれになるんだっけ？っていうメモです。

# 計算

```c#
sbyte sbyteL = 0, sbyteR = 0;
byte byteL = 0, byteR = 0;
...
char charL = '0', charR = '0';

Console.WriteLine("sbyte + sbyte -> {0}", (sbyteL + sbyteR).GetType());
Console.WriteLine("sbyte + byte -> {0}", (sbyteL + byteR).GetType());
...
Console.WriteLine("char + char -> {0}", (charL + charR).GetType());
```

みたいな感じでやった結果が以下の通り。
Visual Studio 2022.NET 6.0でやって明らかに数値じゃない型とか小文字のエイリアスがない型は除外しています。

## サイズ、符号有無順

| 左＼右  | sbyte      | byte    | short      | ushort  | char    | int        | uint    | long       | ulong      | float      | double     | decimal    |
| :------ | :--------- | :------ | :--------- | :------ | :------ | :--------- | :------ | :--------- | :--------- | :--------- | :--------- | :--------- |
| sbyte   | int        | int     | int        | int     | int     | int        | long    | long       | **CS0034** | float      | double     | decimal    |
| byte    | int        | int     | int        | int     | int     | int        | uint    | long       | ulong      | float      | double     | decimal    |
| short   | int        | int     | int        | int     | int     | int        | long    | long       | **CS0034** | float      | double     | decimal    |
| ushort  | int        | int     | int        | int     | int     | int        | uint    | long       | ulong      | float      | double     | decimal    |
| char    | int        | int     | int        | int     | int     | int        | uint    | long       | ulong      | float      | double     | decimal    |
| int     | int        | int     | int        | int     | int     | int        | long    | long       | **CS0034** | float      | double     | decimal    |
| uint    | long       | uint    | long       | uint    | uint    | long       | uint    | long       | ulong      | float      | double     | decimal    |
| long    | long       | long    | long       | long    | long    | long       | long    | long       | **CS0034** | float      | double     | decimal    |
| ulong   | **CS0034** | ulong   | **CS0034** | ulong   | ulong   | **CS0034** | ulong   | **CS0034** | ulong      | float      | double     | decimal    |
| float   | float      | float   | float      | float   | float   | float      | float   | float      | float      | float      | double     | **CS0019** |
| double  | double     | double  | double     | double  | double  | double     | double  | double     | double     | double     | double     | **CS0019** |
| decimal | decimal    | decimal | decimal    | decimal | decimal | decimal    | decimal | decimal    | decimal    | **CS0019** | **CS0019** | decimal    |

## 符号有無、サイズ順

| 左＼右  | sbyte      | short      | int        | long       | byte    | ushort  | char    | uint    | ulong      | float      | double     | decimal    |
| :------ | :--------- | :--------- | :--------- | :--------- | :------ | :------ | :------ | :------ | :--------- | :--------- | :--------- | :--------- |
| sbyte   | int        | int        | int        | long       | int     | int     | int     | long    | **CS0034** | float      | double     | decimal    |
| short   | int        | int        | int        | long       | int     | int     | int     | long    | **CS0034** | float      | double     | decimal    |
| int     | int        | int        | int        | long       | int     | int     | int     | long    | **CS0034** | float      | double     | decimal    |
| long    | long       | long       | long       | long       | long    | long    | long    | long    | **CS0034** | float      | double     | decimal    |
| byte    | int        | int        | int        | long       | int     | int     | int     | uint    | ulong      | float      | double     | decimal    |
| ushort  | int        | int        | int        | long       | int     | int     | int     | uint    | ulong      | float      | double     | decimal    |
| char    | int        | int        | int        | long       | int     | int     | int     | uint    | ulong      | float      | double     | decimal    |
| uint    | long       | long       | long       | long       | uint    | uint    | uint    | uint    | ulong      | float      | double     | decimal    |
| ulong   | **CS0034** | **CS0034** | **CS0034** | **CS0034** | ulong   | ulong   | ulong   | ulong   | ulong      | float      | double     | decimal    |
| float   | float      | float      | float      | float      | float   | float   | float   | float   | float      | float      | double     | **CS0019** |
| double  | double     | double     | double     | double     | double  | double  | double  | double  | double     | double     | double     | **CS0019** |
| decimal | decimal    | decimal    | decimal    | decimal    | decimal | decimal | decimal | decimal | decimal    | **CS0019** | **CS0019** | decimal    |



## エラー

| コード | 意味 |
| :----- | :--- |
| [CS0034](https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/compiler-messages/cs0034) | 型 'type1' および 'type2' のオペランドの演算子 'operator' があいまいです |
| [CS0019](https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/compiler-messages/cs0019) | 演算子 'operator' を 'type' と 'type' 型のオペランドに適用することはできません |

## まとめると

- 整数と整数
  - 16ビット以下整数またはintの組み合わせはintになる
  - 32ビット以上符号なし整数 + 符号付き整数は符号なし整数より1つ大きい符号付き整数になる
    - uint + 符号付き整数はlongになる
    - ulong + 符号付き整数はCS0034エラーになる(ulongより大きい符号付き整数が基本の型にないから？)
  - 32ビット以上符号なし整数 + 符号なし整数は大きい方になる
    - uint + 32ビット以下符号なし整数はuintになる
    - ulong + 符号なし整数はulongになる
  - long + ulong以外の整数はlongになる
- 整数と小数
  - 整数 + 小数は小数の方になる
- 小数と小数
  - decimal + IEEE754はCS0019エラーになる
  - IEEE754の組み合わせは大きい方になる
    - float + doubleはdoubleになる

`[CLSCompliant(true)]`に限定すると

- 整数と整数
  - int以下の組み合わせはintになる
  - longとの組み合わせはlongになる
- 整数と小数
  - 整数 + 小数は小数の方になる
- 小数と小数
  - decimal + IEEE754はCS0019エラーになる
  - IEEE754の組み合わせは大きい方になる
    - float + doubleはdoubleになる

すごいシンプルになりますね、ulongとか`CLSCompliant(false)`なのでCS0034の出番がなくなるし。

### なんでこんな表を作っていたかというと

`dynamic`が使用できない設定にしたクラスライブラリ上で`IComparable<T>`なT型の値T fromからT toまでをステップ値がV型の値V stepで刻む(基本的にV=T想定だけど)`IEnumerable<T>`インターフェイス実装のクラスで`new Stepper(T from, T to, V step)`みたいなことができるものを作りたくて

- V stepが数値型でいう1相当の値だった場合はインクリメント演算子があればインクリメント演算子
- V stepが数値型でいう-1相当の値だった場合はデクリメント演算子があればデクリメント演算子
- V stepが上記以外か対応するインクリメント演算子またはデクリメント演算子がない場合は加算演算子

という演算子オーバーロードをリフレクション拾って実現できないかと小細工を用意してやってみたら

- 圧倒的に出番があるはずの小文字の別名を持つ型の大半から演算子オーバーロードを取得できない(専用の仕組みで頑張っているらしい)
- 演算子オーバーロードのあるdecimalもdecimalとの計算分しか用意していない(implicitとか見て頑張っているらしい)
- int型の値をobject型の変数に格納したものを(long)ってやると型を合わせてくれるどころかエラーになるので、正確に戻した上でキャストする必要がある
  - 小文字の型は基本`IConvertible`インターフェイスを実装していてToXXX()メソッドがあったり、[Convert.ChangeType(object, Type)](https://learn.microsoft.com/ja-jp/dotnet/api/system.convert.changetype?view=net-6.0#system-convert-changetype(system-object-system-type))でどちらかの型に合わせるのは可能なので、あとは2つの型で来た時にどちらに合わせれば良いかの変換表を作れば良い

という感じの結果になってしまい、異なる型での計算時の対応表を作ったりしていたんですね。
他にも二項演算を自由にできるようにという厳密な型指定の言語で何しようとしているのお前みたいなことをしていました。
`dynamic`を使えればこの辺りは楽に書けるんですが、諸事情で今作っている部分には使えないので泥臭い実装をしています。(使える環境でも実行時にCS0034とCS0019の出るところはエラーになる)