【Kotlin】負数の除算・剰余

## 要約

被除数が負のときに困ったら、`/` 演算子と `%` 演算子の代わりに、[`floorDiv` 関数](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/floor-div.html)と [`mod` 関数](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/mod.html) を使うことを検討してみてください。


## 困りごと

曜日を表す列挙クラス `DayOfWeek` があります。

```Kotlin
enum class DayOfWeek {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
}
```

`currentDayOfWeek` は今日の曜日を返すプロパティです。
今日は土曜日です。

```Kotlin
println(
    currentDayOfWeek
) // > SATURDAY
```

明日は何曜日でしょうか。

```Kotlin
println(
    DayOfWeek.values()[
        currentDayOfWeek.ordinal + 1
    ]
) // > SUNDAY
```

日曜日ですね。

では明後日は何曜日でしょうか。

```Kotlin
println(
    DayOfWeek.values()[
        currentDayOfWeek.ordinal + 2
    ] // ArrayIndexOutOfBoundsException がスローされる！
)
```

おっと、これではだめですね。
配列のサイズを超えてしまっています。

```Kotlin
println(
    currentDayOfWeek.ordinal + 2
) // > 7
```

修正しましょう。

```Kotlin
println(
    DayOfWeek.values()[
        (currentDayOfWeek.ordinal + 2) % DayOfWeek.values().size
    ]
) // > MONDAY
```

明後日は月曜日でした。

7日後は？

```Kotlin
println(
    DayOfWeek.values()[
        (currentDayOfWeek.ordinal + 7) % DayOfWeek.values().size
    ]
) // > SATURDAY
```

14日後は？

```Kotlin
println(
    DayOfWeek.values()[
        (currentDayOfWeek.ordinal + 14) % DayOfWeek.values().size
    ]
) // > SATURDAY
```

どちらも土曜日ですね。

では…7日前は？

```Kotlin
println(
    DayOfWeek.values()[
        (currentDayOfWeek.ordinal - 7) % DayOfWeek.values().size
    ] // ArrayIndexOutOfBoundsException がスローされる！
)
```

あれ、なぜでしょう、配列の範囲外になってしまいました。

指定したインデックスの値を確認してみましょう。

```Kotlin
println(
    (currentDayOfWeek.ordinal - 7) % DayOfWeek.values().size
) // > -2
```

なんと、剰余が負になっています！


## 原因

多くのプログラミング言語では truncated division という方法で除算を行います。
Kotlin の標準の数値型の `/` 演算子および `%` 演算子もこの方法を使います。

この方法では -6 〜 +6 を +3 で割ったときの商と剰余は次のようになります。

| 被除数 | 除数 | 商 | 剰余 |
| --- | --- | --- | --- |
| +6 | +3 | +2 |  0 |
| +5 | +3 | +1 | +2 |
| +4 | +3 | +1 | +1 |
| +3 | +3 | +1 |  0 |
| +2 | +3 |  0 | +2 |
| +1 | +3 |  0 | +1 |
|  0 | +3 |  0 |  0 |
| -1 | +3 |  0 | -1 |
| -2 | +3 |  0 | -2 |
| -3 | +3 | -1 |  0 |
| -4 | +3 | -1 | -1 |
| -5 | +3 | -1 | -2 |
| -6 | +3 | -2 |  0 |

また、-3 で割ったときは次のようになります。

| 被除数 | 除数 | 商 | 剰余 |
| --- | --- | --- | --- |
| +6 | -3 | -2 |  0 |
| +5 | -3 | -1 | +2 |
| +4 | -3 | -1 | +1 |
| +3 | -3 | -1 |  0 |
| +2 | -3 |  0 | +2 |
| +1 | -3 |  0 | +1 |
|  0 | -3 |  0 |  0 |
| -1 | -3 |  0 | -1 |
| -2 | -3 |  0 | -2 |
| -3 | -3 | +1 |  0 |
| -4 | -3 | +1 | -1 |
| -5 | -3 | +1 | -2 |
| -6 | -3 | +2 |  0 |

グラフにすると次のようになります。

![truncated division による商と剰余のグラフ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61280/c2ebbe1c-ddb7-3aa2-7009-9a9c4cde093d.png)  
[Quotient and remainder as functions of dividend, using truncated division](https://en.wikipedia.org/wiki/File:Divmod_truncated.svg) / [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/deed.ja)

グラフの読み方：
- 左が除数が正の場合、右が負の場合
- <font color="red">赤が商</font>、<font color="green">緑が剰余</font>
- 横軸が被除数
- 縦軸が商および剰余の値

このように、被除数の正負が変わると剰余の繰り返しパターンが変わってしまうことが、今回の困りごとの原因です。

## 解決方法

`/` 演算子と `%` 演算子の代わりに、[`floorDiv` 関数](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/floor-div.html)と [`mod` 関数](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/mod.html) を使いましょう。

これらは floored division という方法を使います。

この方法では -6 〜 +6 を +3 で割ったときの商と剰余は次のようになります。

| 被除数 | 除数 | 商 | 剰余 |
| --- | --- | --- | --- |
| +6 | +3 | +2 |  0 |
| +5 | +3 | +1 | +2 |
| +4 | +3 | +1 | +1 |
| +3 | +3 | +1 |  0 |
| +2 | +3 |  0 | +2 |
| +1 | +3 |  0 | +1 |
|  0 | +3 |  0 |  0 |
| -1 | +3 | -1 | +2 |
| -2 | +3 | -1 | +1 |
| -3 | +3 | -1 |  0 |
| -4 | +3 | -2 | +2 |
| -5 | +3 | -2 | +1 |
| -6 | +3 | -2 |  0 |

また、-3 で割ったときは次のようになります。

| 被除数 | 除数 | 商  | 剰余 |
| --- | --- | --- | --- |
| +6 | -3 | -2 |  0 |
| +5 | -3 | -2 | -1 |
| +4 | -3 | -2 | -2 |
| +3 | -3 | -1 |  0 |
| +2 | -3 | -1 | -1 |
| +1 | -3 | -1 | -2 |
|  0 | -3 |  0 |  0 |
| -1 | -3 |  0 | -1 |
| -2 | -3 |  0 | -2 |
| -3 | -3 | +1 |  0 |
| -4 | -3 | +1 | -1 |
| -5 | -3 | +1 | -2 |
| -6 | -3 | +2 |  0 |

グラフにすると次のようになります。

![Quotient and remainder as functions of dividend, using floored division](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61280/badff011-312e-9023-18f6-8b86dce66199.png)  

[Quotient and remainder as functions of dividend, using floored division](https://en.wikipedia.org/wiki/File:Divmod_floored.svg) / [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/deed.ja)

グラフの読み方：
- 左が除数が正の場合、右が負の場合
- <font color="red">赤が商</font>、<font color="green">緑が剰余</font>
- 横軸が被除数
- 縦軸が商および剰余の値

このように、被除数の正負によらず剰余の繰り返しパターンが一定です。

なお、当然ですが、次の式が成り立つことは変わりません。
```
被除数 ＝ 除数 × 商 ＋ 剰余
```

これを今回の困りごとに適用すると…

```Kotlin
println(
    DayOfWeek.values()[
        (currentDayOfWeek.ordinal - 7).mod(DayOfWeek.values().size)
    ]
) // > SATURDAY
```

期待通り土曜日になりましたね！

## 参考

[Modulo - Wikipedia](https://en.wikipedia.org/wiki/Modulo)

／以上