Swiftの数値関連のプロトコル

## この記事を書くきっかけ

最近NBTファイルを解析するためのライブラリをSwiftで作りました。
作る前にGithubで探して、swiftのライブラリが出てこなかったような気がしますが、
作り終わってもう一回探してみたら、２つもありました(O.O)....

１つはObjective-CのライブラリをSwiftで使ってるだけなので、無視します。
もう１つは今年2020年前半に作られたもので、ソースファイルをみると、
`FixedWidthInteger`とか、`BinaryInteger`とか、知らないものがたくさんありましたし、
自分が書いたものよりはるかに上手だから少しショックでした。

調べてみると、
`FixedWidthInteger`などはSwiftのStandard Libraryにあるプロトコルのことです。
Swiftで整数をうまく扱うために、
Apple Developer DocumentationとStandard Libraryを読むことにします。
忘れた時のために、そのまとめを記事として残します。

## まずは標準の整数型

* 符号つき整数型：
`Int`, `Int8`, `Int16`, `Int32`, `Int64`
* 符号なし整数型：
`UInt`, `UInt8`, `UInt16`, `UInt32`, `UInt64`

この中で、`UInt8`は1バイトの値を扱う時に役に立ちます。

## プロトコルの適合関係（整数型）

緑の適合関係をとりあえず無視してください。
![プロトコルの関係.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594064/8f6cd014-d158-0a70-35ba-a857caafaca1.png)

## [Equatableプロトコル](https://developer.apple.com/documentation/swift/equatable)
Swiftの標準のデータ型のほとんどが、このプロトコルに適合しています。
このプロトコルに適合する場合、２つの値が同じかどうかを比較するために、
演算子`==`の動作を定義する必要があります。
(`!=`もありますが、`==`の戻り値を逆転しただけ)
配列などで`contains(_:)`メソッドを使えるのは、配列の要素がEquatableプロトコルに準拠しているからです。

ただ、下の条件を満たせば、`==`演算子を定義せずに、`: Equatable`と書くだけで大丈夫です
1. 構造体なら、その全ての`stored properties`がEquatableプロトコルに適合しています
2. 列挙型なら、`associated value`がない、またはその全てがEquatableプロトコルに適合している
条件を満たさない場合、staticメソッドとして`==`を定義する必要があります

```swift
static func == (lhs: Self, rhs: Self) -> Bool
```

実装しなければならないメソッドはこの１つだけとなりますが、他のオプションもあります。
詳しくは見出しのリンク先を参照してください

## [AdditiveArithmeticプロトコル](https://developer.apple.com/documentation/swift/additivearithmetic)

このプロトコルのおかげで、同じ型同士で足し算/引き算ができます。
このプロトコルに適合するには：
1. `: AdditiveArithmetic`
2. `zero`という名前のstatic propertyを追加する。`zero`の型はAdditiveArithmeticプロトコルに適合する。このプロパティは大きさを表すためのもので、インスタンス生成時のデフォルト値となります(Intの0)
3. `+` `+=` `-` `-=`を定義します

## [Numericプロトコル](https://developer.apple.com/documentation/swift/numeric)

このプロトコルのおかげで、同じ型同士で掛け算ができます。
このプロトコルに適合するには：
1. `: Numeric`
2. 必要なイニシャライザと、`*` `*=`演算子を定義します
3. `Magnitude`というassociatedtypeを追加する。これは大きさを表せるものです
4. `magnitude`というインスタンスプロパティを追加する。これは大きさを表すためのプロパティです

## [BinaryIntegerプロトコル](https://developer.apple.com/documentation/swift/binaryinteger) と [SignedNumericプロトコル](https://developer.apple.com/documentation/swift/signednumeric)

この２つのプロトコルは、Numericプロトコルに適合しています。

SignedNumericプロトコルは、`-`演算子を拡張して、数値の正負を反転できるようにします。
デフォルトでプロトコル内で拡張した`-`と`negate()`が実装されているので、
このプロトコルに適合しても、特に何もしなくても良いです。

```swift
var number = 100
let a = -number          // a = -100
print(number)            // 100
// negate()は自身の値を変えてしまいます
let b = number.negate()  // b = -100
print(number)            // -100
```

カスタマイズしたい場合、`mutating func negate()`を新しく実装してください。
また、符号付き整数の場合、表せる負の数は正の数よりも１つ多いので、
その最小の負数を反転するとき、特別の値を返す、またはエラーを投げる必要があります。

---

BinaryIntegerプロトコルは、Swiftの標準で提供される全ての整数型の元となります。
**算術・論理・比較・複合代入・ビット演算子はここで定義/拡張されています。**
このプロトコルに適合れば、違う型でも大小関係を比べられます。(例えば`Int(64)<UInt32(100)`がtrue)

浮動小数点数型や他の整数型から、BinaryIntegerプロトコルに適合したインスタンスを生成するために、４つの変換方法が用意されています。

### 1. 各型のデフォルトイニシャライザを使った変換 (Range-Checked Conversion)

もし変換する値が、対象型が表せる範囲内でなければ、ランタイムエラーが起こります。

```swift
let a = Int16(500)  // 500
let b = UInt8(500)  // error
```

### 2. `init?(exactly:)`を使った変換 (Exact Conversion)

与えられた数値を、この型で表せるなら、オプショナルのインスタンスが生成されます。
そうでなければ、nilとなります

```swift
let a = Int16(exactly: 500)  // Optional(500)
let b = UInt8(exactly: 500)  // nil
```

また、浮動小数点数が与えられた場合、整数かどうかもチェックされます
(整数でなければnilが返されます)

### 3. `init(clamping:)`を使った変換 (Clamping Conversion)

２つ目と違って、範囲外の数値が与えられた場合、この型の最大値または最小値に変えられます。

```swift
// Int8.min :   -128
// Int8.max :   127
Int8(clamping: -200)   // -128
Int8(clamping: 300)    // 127
```

### 4. `init(truncatingIfNeeded:)`を使った変換 (Bit Pattern Conversion)

2進数表現での変換と思えばわかりやすいでしょう
例えば2バイトの`0b00000000 00011101`を１バイトに変換したり、4バイトに変換したりします。
必要に応じて足りない部分を(2進数の)0または1で埋めたり、溢れ出した部分をカットしたりします。

```swift
let number01: Int16 = 9843                   // 0b00100110_01110011
// よりバイト数が少ない型に変換。一部だけ切り取るので値が変わる
let a = Int8(truncatingIfNeeded: number01)   // 0b01110011 = 115
// よりバイト数が多い型に変換。正の数なので0で先頭を埋める
let b = Int32(truncatingIfNeeded: number01)  // 0b00000000_00000000_00100110_01110011 = 9843

let number01: Int8 = -100                    // 0b10011100
// 符号つき整数から符号なし整数に変換、値が変わる
let c = UInt8(truncatingIfNeeded: number02)  // 0b10011100 = 156
// よりバイト数が多い型に変換。負の数なので1で先頭を埋める
let d = Int16(truncatingIfNeeded: number02)  // 0b11111111_10011100 = -100
```

ちなみに、Swiftの整数値はリトルエンディアン(little endian ⇆ big endian)です。
どういうことかと言うと、
上の例では、Int16型の9843のビッグエンディアン表現は`00100110_01110011`ですが、
内部では`01110011_00100110`として保存されています。
Int8型に変換する際に、一番最初の1バイトの`01110011`だけ切り取ります。
例だけを見れば、後ろから1バイトずつ切り取っていくように感じてしまうかもしれませんが、内部の動作が違います。

## [FixedWidthIntegerプロトコル](https://developer.apple.com/documentation/swift/fixedwidthinteger)

このプロトコルは、BinaryIntegerプロトコルで使える演算子に、ビットごとの演算(`&` `|` `^` `~`など)、ビットシフト(`>>` `<<`など)、そしてオーバーフロー時の処理を追加します。

ビットごとに計算したり、ビットシフトをしたり、オーバーフローを受け取ったり、型が表せる最大値＆最小値を取得したりする際に、FixedWidthIntegerプロトコルを制限として適合する、または機能を拡張して適合することが良いでしょう。
(もっと早くこれを知ればよかったT-T)

２つの公式の例をそのまま借ります。
１つ目は、整数値を２進数の文字列に変換するものです

```swift
extension FixedWidthInteger {
    var binaryString: String {
        var result: [String] = []
        for i in 0..<(Self.bitWidth / 8) {
            let byte = UInt8(truncatingIfNeeded: self >> (i * 8))
            let byteString = String(byte, radix: 2)
            let padding = String(repeating: "0",
                                 count: 8 - byteString.count)
            result.append(padding + byteString)
        }
        return "0b" + result.reversed().joined(separator: "_")
    }
}

print(Int16.max.binaryString)
// Prints "0b01111111_11111111"
print((101 as UInt8).binaryString)
// Prints "0b11001001"
```

２つ目は、整数値の２乗を計算するものです。
オーバーフローの対応として、nilを返すようにしています。

```swift
func squared<T: FixedWidthInteger>(_ x: T) -> T? {
    let (result, overflow) = x.multipliedReportingOverflow(by: x)
    if overflow {
        return nil
    }
    return result
}

let (x, y): (Int8, Int8) = (9, 123)
print(squared(x))
// Prints "Optional(81)"
print(squared(y))
// Prints "nil"
```

自作の型にFixedWidthIntegerプロトコルに適合するには：
1. `: FixedWidthInteger`
2. `init(bigEndian: Self)`と`init(littleEndian: Self)`を定義する
3. 必要な８つのプロパティを定義する(詳しくは見出しのリンクより参照)
4. `ReportingOverflow`で終わるメソッドを全て定義する(詳しくは見出しのリンクより参照)
これ以外のメソッドはstandard libraryが提供してくれます

## [UnsignedIntegerプロトコル](https://developer.apple.com/documentation/swift/unsignedinteger)と[SignedIntegerプロトコル](https://developer.apple.com/documentation/swift/signedinteger)

負数じゃない数だけを表せるUnsignedIntegerプロトコル、
正数でも負数でも表せるSignedIntegerプロトコル、
どちらも静的プロパティ`max` `min`を持っています。

## 勉強することが多い...

ここまでは赤い矢印の適合関係にあるプロトコルでした。
ここからはBinaryIntegerプロトコルが適合したプロトコルを詳しく調べていきます......

と思いましたが、他に読みたいものができたので、
またいつか時間がある時に更新したいと思います。

SwiftやiOS開発関係の本２冊合わせて1000ページくらいの鈍器を入手したので、
最近はこれらを読みながら、UIKitのドキュメントも覗いてみます。
あと来月に応用情報がありますが、受かる気がしないですけどそれの勉強もしないと。。。。

あぁぁぁぁぁぁぁーーーーーーーーーーもっと時間がほしいいいいいいい (ಥ_ಥ)