1.0とその隣人:浮動小数点のほんの少しの旅

## TL;DR
* Swiftで「`1.0`より大きい最小の値」は`(1.0).nextUp`です。
* 境界値テストで"1.0"に対する境界値に"1.1"を使っていませんか？
* [IEEE 754](https://ja.wikipedia.org/wiki/IEEE_754)に準拠していれば他の言語でも応用できます。

## 1.0の隣人は誰か？

https://techblog.lycorp.co.jp/ja/20231116a

こちらの記事がきっかけなのでぜひご一読ください。
入力が[0.0〜1.0]の範囲に限定されているメソッドを使いたいときに、入力が範囲内であることを保証するオブジェクトを使うというテクニックが紹介されていました。

読んでいて「テストの時はどうするの？」と疑問が湧きました。
境界値テストで、最大値の`1.0`より大きい値を入力したい場合、値は何がふさわしいでしょうか？🤔

`1.1`でしょうか？いえ、`1.0`と`1.1`の中間には`1.05`があります。なので`1.1`は境界値としてふさわしくありません。
`1.0`と`1.05`の中間には`1.025`があります。
`1.0`と`1.025`の中間には`1.0125`があります。
...と永遠にやっていられそうです。😅

結論から言うと、`Swift`の`Double`には`nextUp`というプロパティがあります。
`(1.0).nextUp`を使うと「`1.0`より大きい最小の値」が得られます。
`1.0`と`(1.0).nextUp`の中間の値はありません。(倍精度浮動小数点形式の表現内では)
逆に、`(0.0).nextDown`を使うと「`0.0`より小さい最大の値」が得られます。

## サンプルコード
`Kotlin`のサンプルコードを`Swift`に直したコードとテストコードは以下のようになります。

```Swift:ProgressRatio.swift
/// 値が0.0~1.0の範囲に入っていることを保証する.
/// 範囲外であればnilを返す.
struct ProgressRatio {
    let value: Double
    init?(value: Double) {
        guard (0.0...1.0) ~= value else {
            return nil
        }
        self.value = value
    }
}
```

```Swift:ProgressRatioTests.swift
import XCTest
@testable import YourModule

final class ProgressRatioTests: XCTestCase {
    func testProgressRatio() {
        let minValue = 0.0
        let maxValue = 1.0
        
        XCTContext.runActivity(named: "最小値を入力した場合") { _ in
            if let progressRatio = ProgressRatio(value: minValue) {
                XCTAssertEqual(progressRatio.value, minValue,
                               "valueに入力値(\(minValue))が設定されていること")
            } else {
                XCTFail("最小値を入力したらnilが返されないこと")
            }
        }
        
        XCTContext.runActivity(named: "最大値を入力した場合") { _ in
            if let progressRatio = ProgressRatio(value: maxValue) {
                XCTAssertEqual(progressRatio.value, maxValue,
                               "valueに入力値(\(maxValue))が設定されていること")
            } else {
                XCTFail("最大値を入力したらnilが返されないこと")
            }
        }
        
        XCTContext.runActivity(named: "最小値より小さい値を入力した場合") { _ in
            let result = ProgressRatio(value: minValue.nextDown)
            XCTAssertNil(result, "(\(minValue)).nextDownを入力したらnilが返されること")
        }
        
        XCTContext.runActivity(named: "最大値より大きい値を入力した場合") { _ in
            let result = ProgressRatio(value: maxValue.nextUp)
            XCTAssertNil(result, "(\(maxValue)).nextUpを入力したらnilが返されること")
        }
    }
}
```

## 疲れた 😴💤

[浮動小数点](https://ja.wikipedia.org/wiki/浮動小数点数)の話を書こうと思ったけど、話が膨らまない気がしてきたので、何か思いついたら書きます🙏

倍精度浮動小数点数の形式
<img width="618" src="https://upload.wikimedia.org/wikipedia/commons/a/a9/IEEE_754_Double_Floating_Point_Format.svg">
`1.0` = (&minus;1)<sup>**0**</sup> × (1 &plus; **0**) × 2<sup>(**1023** &minus; 1023)</sup>
= 0011111111110000000000000000000000000000000000000000000000000000<sub>double</sub>

`(1.0).nextUp` = (&minus;1)<sup>**0**</sup> × (1 &plus; **2<sup>-52</sup>**) × 2<sup>(**1023** &minus; 1023)</sup>
= 0011111111110000000000000000000000000000000000000000000000000001<sub>double</sub>
= 1.0000000000000002220446049250313080847263336181640625<sub>10</sub>


## 参照
[Swift / Double : A double-precision, floating-point value type.](https://developer.apple.com/documentation/swift/double)
[IEEE 754-2008 - IEEE Standard for Floating-Point Arithmetic](https://ieeexplore.ieee.org/document/4610935/)