Help us understand the problem. What is going on with this article?

Kotlinでの負の数の剰余演算の仕様を実験で確かめた(+使った便利なKotlin文法の紹介)

More than 3 years have passed since last update.

最近 Kotlin (Javaと似てるプログラミング言語) を始めました。練習がてらいろいろ書いているのですが、そこで負の数の剰余演算を扱う機会があって微妙に期待通りの出力が得られなかったので、剰余演算の仕様を検証しました。

数学的な話も結構挟まるので結論だけほしい人はそこまで飛ばしてください。Kotlinの文法の話だけ見たい人はもっと飛ばしてください。

そもそも剰余演算とは

「剰余演算とは」について「そんなのわかりきってる」と思う人も多いと思いますが、私と異なる認識を持っているとこの先「この結果はおかしい」か「この結果はおかしくない」かを考えるときに支障をきたすので、私が何を期待しているかを述べておきます。

端的に言うと、整数 $a$ を整数 $b$ で割る剰余演算とは

a = bx + y,\;\; 0\leq y <|b|

のただ一通りしかない整数解 $(x,y)$ のうち $y$ の方を得る演算であると考えています。

この $y$ のことを $a$ 割る $b$ の剰余と呼びます。例えば、10=3x+y, 0≦y<|3| の整数解は (x,y)=(3,1) ですから、10を3で割った剰余は1です。小学校で習う余りのある割り算のあまりと同じですね。多くのプログラミング言語はこの剰余を計算する演算子として % があり、a%b で計算できます。

$0\leq y <|b|$ はこの方程式の整数解が一通りしかないようにするための制約です。算数でも「10÷3=2あまり4」とか言うと「余り過ぎ、もっと分けられるでしょう?」となりますが、それを数式で表現したものです。こうすると整数解が一通りになることは数学的に証明できます。

これが私が期待する(そして都合が良い)剰余演算ですが、結論から言うと、Kotlinの剰余演算は負の数が関わった場合に私の期待通りの結果になっていませんでした。

実験内容と結果と検証

まずは割る数が正の数 (3) である場合を検証しました。以下のプログラムでは、-10÷3, -9÷3, ..., 10÷3 と割られる数を1ずつ増やしながら剰余演算をしています。(Kotlinの文法については別の節で説明します)

modCheck1.kt
fun main(args : Array<String>) {
    val b=3
    for(a in -10..10) println("$a%$b = ${a%b}")
}

結果は以下の通りです。

-10%3 = -1
-9%3 = 0
-8%3 = -2
-7%3 = -1
-6%3 = 0
-5%3 = -2
-4%3 = -1
-3%3 = 0
-2%3 = -2
-1%3 = -1
0%3 = 0
1%3 = 1
2%3 = 2
3%3 = 0
4%3 = 1
5%3 = 2
6%3 = 0
7%3 = 1
8%3 = 2
9%3 = 0
10%3 = 1

正の数÷正の数についてはこの結果に異論を唱える者はいないでしょう。問題は負の数÷正の数です。たとえば、-10%3 = -1となっていますが、私の期待通りなら-1ではなく2になっているはずです(-10=3x+y, 0≦y<|3| の整数解は x=-4, y=2)。

ただ、よくよく見てみると $a=bx+y$ の整数解にはなっています。例えば、-10÷3は $0\leq y<|3|$ という制約を取り除けば $(x,y)=(-3,-1)$ も解になります。つまり、解を一通りにするための制約である $0\leq y<|b|$ さえなければこの結果でもよいのです。そして、剰余類環などを学んでいるとよくわかるのですが、剰余 $y$ が $a=bx+y$ さえ満たしていてくれれば問題ない場面も多いので気にしなくてもよい場合が多そうです。

これが気になる場面では、得た剰余が負であるときに$b$を足すことで正の解を得ることができます。-10%3 = -1の例だと、$-10=3x+y$ の整数解として $(x,y)=(-3,-1)$ を採用していますが、$x$ を1減らせば $-10=3x+y$ より $y$ は3増えることになります。こうして別の整数解 $(x,y)=(-3-1,-1+3)=(-4,2)$ を得ることができますが、これは当初期待していたものになっています。(大抵のプログラミング言語では絶対値が小さい$y$を剰余として採用するので起こらないと思いますが、bを足してもなお負の数のままだったら正になるまで何回もbを足しましょう。)

ちなみに、負の数である-3で割るプログラムと結果が以下の通りです。出力結果が正の数3で割った時と全く変わりませんね。したがって、上と全く同じ議論が展開できます。

modCheck2.kt
fun main(args : Array<String>) {
    val j=-3
    for(i in -10..10) println("$i%$j = ${i%j}")
}
-10%-3 = -1
-9%-3 = 0
-8%-3 = -2
-7%-3 = -1
-6%-3 = 0
-5%-3 = -2
-4%-3 = -1
-3%-3 = 0
-2%-3 = -2
-1%-3 = -1
0%-3 = 0
1%-3 = 1
2%-3 = 2
3%-3 = 0
4%-3 = 1
5%-3 = 2
6%-3 = 0
7%-3 = 1
8%-3 = 2
9%-3 = 0
10%-3 = 1

結論

Javaと同じ。

a%ba=bx+yの整数解yを返すことには間違いないので、それさえ満たしていればよい場面では特別なことは何もせずに剰余演算を使えます。

mod.kt
fun main(args : Array<String>) {
    val a = -10; val b = 3

    //結果が負になってもいいなら
    val result = a % b
}

0≦a%b<|b| になってほしい場合、結果が負の数だったらbを足しましょう。

mod2.kt
fun main(args : Array<String>) {
    val a = -10; val b = 3

    //結果が非負であってほしいなら
    val result = if(a % b < 0) a % b + b else a % b
}

使用したKotlinの文法

最後にKotlinの便利さを布教するためのおまけです。読者として何かしらの言語で基本的なプログラミングができる者を想定しています。

この節については、私がKotlinをまだ勉強中であることから、勘違いやより便利な方法の見落としがあるかもしれません。指摘がある方はコメントでお願いします。

この節では、上で取り上げた2つの例

modCheck1.kt
fun main(args : Array<String>) {
    val b=3
    for(a in -10..10) println("$a%$b = ${a%b}")
}
mod2.kt
fun main(args : Array<String>) {
    val a = -10; val b = 3

    //結果が非負であってほしいなら
    val result = if(a % b < 0) a % b + b else a % b
}

で用いているKotlinの便利な文法を紹介します。

グローバル関数が使える

C++などではクラスから独立した関数(グローバル関数)を作れるのは当たり前ですが、Javaではそれができません。Javaでは関数は必ずクラスが持ちます(関数ではなくメソッドと呼びます)。

Kotlinでは関数をクラスの外に作れるので、プログラムの開始時によばれるmain(Array<String>)もクラスの外に作れます。ですから、上のプログラムは単体でそのまま実行できます。今回の簡単なプログラムでは他にクラスを使う要素がないので、無駄にクラスを作らなくて済むのはありがたいです。

標準出力関数のprintln(String)も同様で、JavaではSystem.out.println(String)のようにprintlnメソッドを持っているクラスから書かなくてはいけませんでしたが、Kotlinならprintln(String)で済んでいます。

もちろん、自分で他のグローバル関数を(実はグローバル定数やグローバル変数も)定義することもできます。

定数・変数宣言が楽

Kotlinの変数には型がありますが、型推定をしてくれるので var b = 3 のように型を明示しなくても変数宣言ができます(この例だと整数型Intであると判断されます)。上の例ではmainの引数を除いて一切型を明示していませんがちゃんとコンパイルされるのです。

そして、var なら変数(後から変更可能)、val なら定数(後から代入できない)になります。例えばJavaでは定数にするには打つ文字数が増えるのでなかなか定数を使いませんが、Kotlinでは文字数が増えないので定数を使おうという気になって、定数であるべきかを意識するようになります。

型を明示したい場合は、val b: Int = 3のように書きます。

for文が見やすく書ける

C言語では以下のように書くことで、変数iを1ずつ増やしながら……という処理を実現できます。

forの例 (C)
//iを0から10まで、1ずつ増やしながら繰り返す
int i;
for(i=0; i<=10; i++){
    //なんらかの処理
}

これと同じことをKotlinでは次のように書けます。

forの例 (Kotlin)
//iを0から10まで、1ずつ増やしながら繰り返す
for(i in 0..10){
    //なんらかの処理
}

Kotlinの方の例では IntRangeクラスを使っています。0..10の部分で「0から10までを表すIntRangeクラスのインスタンス」を表しています。そして、IntRangeクラスは Iterable<Int> を実装しているため、JavaのList<>のようにそこから1つずつ取り出してfor文の本文を繰り返すということができるのです。

変数を使って IntRange を作ることもできますし、untilを使えば「a以上b未満の範囲のIntRange」も簡単に書けます。

forの例 (Kotlin)
val a=0
val b=10

//各繰り返しにおけるiの値は、i=0,1,2,3,4,5,6,7,8,9,10
for(i in a..b){
    //なんらかの処理
}

//各繰り返しにおけるiの値は、i=0,1,2,3,4,5,6,7,8,9
for(i in a until b){
    //なんらかの処理
}

//各繰り返しにおけるiの値は、i=0,1,2,3,4,5,6,7,8,9
for(i in a.until(b)){
    //なんらかの処理
}

for文のインデックスにはvarはつけません。var i in 0..10とは書きません。そして、型推論が働くのでi:Intと型を明示する必要はありません。

注意が必要な点としては、Kotlinのfor文は「Iterableなものから順にとってくるタイプ(Javaで言うところの拡張for文)しか表現できない」ことがあげられます。今のところ不便に思ったことはないのですが、まだまだ勉強中なのでこのことについてはこれ以上コメントはしません。

もう一つ注意が必要な点としては、インデックスをfor文の外に持ち出せない点です。例えば、

Kotlin
fun main(args : Array<String>) {
    var i=100
    for(i in 0 until 5){
        print(i)    //0,1,2,3,4
        print(",")
    }
    print(i)    //100
}

0,1,2,3,4,100を出力します。つまり、たとえ同じ名前の変数があったとしても、for文のインデックスはそれとは別のものとして新たに用意されます。そして、for文の中ではインデックスと同名の変数にはアクセスできなくなりますし(メンバ変数にならthis.iなどでアクセスできると思いますが)、for文の外からfor文で最後に使ったインデックスを参照することもできません。これはC言語に慣れている人には違和感があるのではないかと想像します。私はこちらも不便に思ったことはありませんが。

フォーマット文字列的なやつが楽

フォーマット文字列は、JavaでもCでも

C言語
prints("%d+%d=%d", a, b, a+b)

のように、まず文字列をフォーマット用の記号付きで書いて、そのあとで各%dに当てはまる整数を指定するといった感じになります。Kotlinでは $を文字列中で使うことで

Kotlin
print("$a+$b=${a+b}")

のように変数名や式で直接指定することができます(${式}のように波かっこも用いることで式を指定できます)。そして、文字列インスタンスを生成する段階で変数や式を解釈するので

Kotlin
val a=3
val b=1
str = "$a+$b=${a+b}"    //str = "3+1=4"

のように代入することもできます。C言語でこれをやろうとするとsprintf()を使うことになりますが、だいぶ煩雑なコードになるので私は嫌いです。

一行中に複数のことを書ける

これはC言語やJavaなど行末にセミコロン;を使う言語なら当たり前ですが、セミコロンで区切ることで一行中にいっぱいかけます。

Kotlin
val a = 3; val b = 4

Kotlinはセミコロンを必要としないのですが、あえて使えばこのように一行にまとめて書けます。Pythonでもこんな感じですね。

ifを三項演算子みたいに使える

x>0だったらval a=1にして、そうでなければval a=-1にする」といったことを書こうと思って

Kotlin (コンパイルエラー)
val a = -1
if(x > 0) a = 1    //定数を書き換えようとしている

と書くとコンパイルできません。aは定数として宣言しているので、a=1とあとから書き換えることはできないのです。したがって、aを定数にしたいなら初期化の段階で1-1かを決めなくてはなりません。三項演算子を使えば「x>0のときは1を返してそうでないときは-1を返すような式」が表現できるのでそれをaの初期値とするように書けばよいのですが、Kotlinはこれをifで書けます。

Kotlin
val a = if(x > 0) 1 else -1

細かく言うと、Kotlinのifは最後に評価した値を返すであるといえます。式ですからその結果の値を変数(定数)に代入できるのです。今回の例はJavaでも三項演算子を使って再現できますが、Kotlinではそれをifでできるという点でわかりやすいかなというところです。

k-izumi
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away