0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Kotlin】負数の除算・剰余

Last updated at Posted at 2023-04-01

要約

被除数が負のときに困ったら、/ 演算子と % 演算子の代わりに、floorDiv 関数mod 関数 を使うことを検討してみてください。

困りごと

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

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

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

println(
    currentDayOfWeek
) // > SATURDAY

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

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

日曜日ですね。

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

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

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

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

修正しましょう。

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

明後日は月曜日でした。

7日後は?

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

14日後は?

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

どちらも土曜日ですね。

では…7日前は?

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

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

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

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 による商と剰余のグラフ
Quotient and remainder as functions of dividend, using truncated division / CC BY-SA 3.0

グラフの読み方:

  • 左が除数が正の場合、右が負の場合
  • 赤が商緑が剰余
  • 横軸が被除数
  • 縦軸が商および剰余の値

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

解決方法

/ 演算子と % 演算子の代わりに、floorDiv 関数mod 関数 を使いましょう。

これらは 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

Quotient and remainder as functions of dividend, using floored division / CC BY-SA 3.0

グラフの読み方:

  • 左が除数が正の場合、右が負の場合
  • 赤が商緑が剰余
  • 横軸が被除数
  • 縦軸が商および剰余の値

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

なお、当然ですが、次の式が成り立つことは変わりません。

被除数 = 除数 × 商 + 剰余

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

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

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

参考

Modulo - Wikipedia

/以上

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?