Y121516 の大館お役立ち日記

秋田県大館市での生活

整数型では割り算演算子ではなく商演算子を使う

割り算・商

割り算演算子とは / のことです。商演算子とは \ のことです。

割り算演算子 /浮動小数点型の値を受け取り浮動小数点型の値を返す演算子です。浮動小数点型の結果が欲しければ割り算演算子 / を使います。

演算子 \ は「整数除算演算子」とも呼ばれます。その名の通り、整数型の値を受け取り整数型の値を返す演算子です。整数型の結果が欲しければ商演算子 \ を使います。

プログラムの読みやすさ、およびプログラムの実行速度の観点からこれらを正しく使い分けることは大切です。 そうしないと「なんでこうしたの?」「なんでこんなに遅いコード書いてるの?」と突っ込まれることになります。

割り算演算子 /CInt を使う?

ところで整数型の割り算の値を得るために、割り算演算子 / を使いその結果を CInt で型変換したらダメなのでしょうか?

Dim integerValue As Integer = CInt(integerA / integerB)

ではまず「なぜ CInt で型変換するのですか?」という問いに自ら答えてみましょう。

CInt のコストを払うだけの明確な理由があるならば、そうするしかありません。CInt は四捨五入 (その中の銀行家の丸め banker's rounding) という特殊な、かつ比較的重めの処理をします。銀行家の丸めが本当に必要ならば CInt で型変換しましょう。重い処理になるのはしょうがありません。これ以上の議論は不要です。

一方明確な理由がないのに CInt を使うと、コードを書くにも読むにもノイズでしかありません。なんで四捨五入しているんだろう…と戸惑います。そして無駄に重い処理になります。 ちなみに Option Strict Off だと CInt を書く必要は無くなりますが、結局 CInt と同じ処理が入るので無駄に重い処理になるのは変わりません。無駄に重い処理になっていることが隠されるので Option Strict Off は悪質です。

もう一つコストの話。 最初に説明したとおり割り算演算子 /浮動小数点型の値を受け取る演算子です。 ですから、整数型の値同士の演算に割り算演算子 / を使うと、それら二つの値は浮動小数点型への型変換が入ります。 拡大変換なので、Option Strict On でも明示的な変換を書く必要はありませんが、確実に処理速度の点ではコストがかかります。

最後にCInt浮動小数点型から整数型へまた変換…無駄すぎますよね。

割り算演算子 /CInt を使うと

  • 実行速度の点では四捨五入と型変換という余計な処理が入り遅い
  • コードの読みやすさの点ではノイズであり人を戸惑わせる

演算子(整数除算演算子) \ を使う

型を正しく意識していれば、余計な型変換をする必要がなくなります。

Dim integerValue As Integer = integerA \ integerB

型が揃っているので、型変換のコードを書く必要がなく型変換が起こりません。 例え Option Strict On であったとしても冗長なコードを書く必要はありません。 つまり、コードを書くのも読むのも楽になります。

演算子(整数除算演算子) \ は小数点以下を切り捨てた整数を返します。銀行家の丸めのように重い処理ではなく、非常にプリミティブな処理で高速です。 さらに整数型から浮動小数点型への余計な型変換をしないのでその分も高速になります。

これら両方が相まってプログラムの実行速度が速くなります。手元で確かめたところ「割り算演算子 /CInt を使う」ケースより「商演算子(整数除算演算子) \ を使う」ケースの方が3倍速くなりました。

f:id:y121516:20171209192820p:plain

演算子(整数除算演算子) \ を使うと

  • 実行速度の点では速くなる
  • コードの読みやすさの点では整数型から整数型の商が欲しいことがコードの読み手にストレートに伝わる

コード

Module Program
    Sub Main()
        Const n = 100000000
        Const times = 5

        Dim progression1 = New List(Of Integer)(n)
        Dim progression2 = New List(Of Integer)(n)
        Dim r = New Random()
        For i = 1 To n
            progression1.Add(r.Next() + 1)
            progression2.Add(r.Next() + 1)
        Next
        Dim answer = New Integer(n - 1) {}

        Dim s = New Stopwatch()
        Dim quotient =
            Sub()
                s.Restart()
                For index = 0 To n - 1
                    answer(index) = progression1(index) \ progression2(index)
                Next
                s.Stop()
                Console.WriteLine($"\ 商演算子     {s.Elapsed}")
            End Sub
        Dim division =
            Sub()
                s.Restart()
                For index = 0 To n - 1
                    answer(index) = CInt(progression1(index) / progression2(index))
                Next
                s.Stop()
                Console.WriteLine($"/ 割り算演算子 {s.Elapsed}")
            End Sub
        Dim speedTest =
            Sub()
                quotient()
                division()
            End Sub

        For i = 1 To times
            speedTest()
        Next

        Console.ReadKey()
    End Sub
End Module