できない.dev

整数の割り算で商と余りを求めるには

整数どうしの割り算で商と余りを求める基本形を各言語で示す。
負の数を割ったときに商を 0 の側へ切り捨てるか小さい側へ切り下げるかが言語ごとに違い、それに合わせて余りの符号も変わる点と、0 で割ったときの振る舞いの違いまでを扱う。

公開:

各言語見出しの横のバッジは検証状態を表す。実行確認済みはコードを実際に実行して確認したもの、静的確認は構文と公式 API ドキュメントで確認したものである。

Python 実行確認済み

print(17 // 5, 17 % 5)    # 3 2
print(divmod(17, 5))      # (3, 2)
print(17 / 5)             # 3.4(/ は常に float を返す)
 
# 負の数では商を小さい側へ切り下げ、余りは割る数と同じ符号になる
print(-17 // 5, -17 % 5)  # -4 3
print(divmod(-17, 5))     # (-4, 3)
 
try:
    17 // 0
except ZeroDivisionError:
    print("0 では割れない")  # 0 では割れない

// が商、% が余りを返し、divmod を使うと両方を 1 回で得られる。
// は商を小さい側へ切り下げるので -17 // 5 は -4 になり、余りは割る数と同じ符号の 3 になる。
/ は整数どうしでも float を返すので、整数の商が欲しいときは // を使う。
0 で割ると ZeroDivisionError が送出される。

JavaScript 実行確認済み

const a = 17, b = 5;
console.log(Math.trunc(a / b), a % b); // 3 2
console.log(a / b);                    // 3.4
 
// % の余りは割られる数と同じ符号になる
console.log(Math.trunc(-17 / 5), -17 % 5); // -3 -2
// 0 以上の余りが欲しいときは、割る数を足してからもう一度 % を取る
console.log(((-17 % 5) + 5) % 5);          // 3
 
// 0 で割っても例外にはならない
console.log(17 / 0, 17 % 0);     // Infinity NaN
// 大きな整数は BigInt で割る。/ は小数部を切り捨てる
console.log(17n / 5n, -17n % 5n); // 3n -2n

JavaScript の数値には整数型が無いので、/ で割った結果を Math.trunc で切り捨てて商にし、余りは % で求める。
% の余りは割られる数と同じ符号になり、-17 % 5 は -2 である。
0 で割っても例外にはならず、/ は Infinity、% は NaN を返すので、割る数が 0 でないことを先に確かめる。
BigInt どうしの / は小数部を切り捨てて商を返し、0n で割ったときは RangeError になる。

TypeScript 実行確認済み

// 商と余りを組で返す。0 で割ったときは Infinity や NaN を返さずに例外にする
function divmod(a: number, b: number): [quotient: number, remainder: number] {
  if (!Number.isInteger(a) || !Number.isInteger(b)) {
    throw new TypeError("整数を渡してください");
  }
  if (b === 0) {
    throw new RangeError("0 では割れません");
  }
  return [Math.trunc(a / b), a % b];
}
 
const [q, r] = divmod(17, 5);
console.log(q, r);           // 3 2
console.log(divmod(-17, 5)); // [ -3, -2 ]
 
try {
  divmod(17, 0);
} catch (e) {
  console.log(e instanceof RangeError ? e.message : e); // 0 では割れません
}

divmod は商と余りを名前付きのタプル型で返すので、呼び出し側は分割代入でそれぞれを受け取れる。
0 で割ったときに Infinity や NaN が後の計算へ流れていかないよう、RangeError を投げて呼び出し側に知らせる。
number 型は小数も受け入れるため、Number.isInteger で整数かどうかを確かめてから割る。

Go 実行確認済み

package main
 
import "fmt"
 
func main() {
	a, b := 17, 5
	fmt.Println(a/b, a%b) // 3 2
 
	// 商は 0 の側へ切り捨て、余りは割られる数と同じ符号になる
	c := -17
	fmt.Println(c/b, c%b) // -3 -2
 
	// 0 以上の余りが欲しいときは、負なら割る数を足す
	r := c % b
	if r < 0 {
		r += b
	}
	fmt.Println(r) // 3
 
	fmt.Println(float64(a) / float64(b)) // 3.4
}

整数どうしの / は商を 0 の側へ切り捨て、% の余りは割られる数と同じ符号になる。
0 以上の余りが欲しいときは、負になった余りに割る数を足して補正する。
変数の 0 で割ると実行時に panic: runtime error: integer divide by zero で止まり、a / 0 のように定数の 0 で割る式はコンパイル時に invalid operation: division by zero になる。

Rust 実行確認済み

fn main() {
    let (a, b) = (17_i32, 5_i32);
    println!("{} {}", a / b, a % b); // 3 2
 
    // / と % は 0 の側へ切り捨てる。余りは割られる数と同じ符号になる
    let c = -17_i32;
    println!("{} {}", c / b, c % b); // -3 -2
    // 割る数が正なら、div_euclid / rem_euclid で小さい側への商と 0 以上の余りが得られる
    println!("{} {}", c.div_euclid(b), c.rem_euclid(b)); // -4 3
 
    // 0 で割るとパニックになるので、入力由来の値は checked_div / checked_rem で受ける
    println!("{:?} {:?}", a.checked_div(0), a.checked_rem(0)); // None None
    println!("{:?}", a.checked_div(b)); // Some(3)
}

/ と % は商を 0 の側へ切り捨て、余りは割られる数と同じ符号になる。
割る数が正のとき、div_euclid と rem_euclid は商を小さい側へ切り下げ、0 以上の余りを返す。
0 で割るとパニックするので、入力由来の割る数には Option を返す checked_div と checked_rem を使う。
-17_i32.div_euclid(5) と書くとメソッド呼び出しが先に結び付いて -3 になるため、負のリテラルは括弧で囲むか変数に入れる。

Java 実行確認済み

public class DivMod {
    public static void main(String[] args) {
        int a = 17, b = 5;
        System.out.println(a / b + " " + a % b); // 3 2
 
        // 商は 0 の側へ切り捨て、余りは割られる数と同じ符号になる
        int c = -17;
        System.out.println(c / b + " " + c % b); // -3 -2
        // 商を小さい側へ切り下げ、余りを割る数と同じ符号にするなら floorDiv / floorMod
        System.out.println(Math.floorDiv(c, b) + " " + Math.floorMod(c, b)); // -4 3
 
        System.out.println(a / (double) b); // 3.4
 
        try {
            System.out.println(a / 0);
        } catch (ArithmeticException e) {
            System.out.println("ArithmeticException: " + e.getMessage()); // ArithmeticException: / by zero
        }
    }
}

整数どうしの / は商を 0 の側へ切り捨て、% の余りは割られる数と同じ符号になる。
Python と同じく商を小さい側へ切り下げたいときは、Math.floorDiv と Math.floorMod を使う。
整数を 0 で割ると ArithmeticException が投げられるが、どちらかが double なら例外にはならず、17.0 / 0 は Infinity になる。

C# 実行確認済み

int a = 17, b = 5;
Console.WriteLine($"{a / b} {a % b}"); // 3 2
 
// Math.DivRem は商と余りをタプルで返す(.NET 6 以降)
var (q, r) = Math.DivRem(-17, b);
Console.WriteLine($"{q} {r}"); // -3 -2
 
// 0 以上の余りが欲しいときは、割る数を足してからもう一度 % を取る
Console.WriteLine((-17 % b + b) % b); // 3
 
Console.WriteLine((double)a / b); // 3.4
 
int zero = 0;
try
{
    Console.WriteLine(a / zero);
}
catch (DivideByZeroException e)
{
    Console.WriteLine(e.Message); // Attempted to divide by zero.
}

整数どうしの / は商を 0 の側へ切り捨て、% の余りは割られる数と同じ符号になる。
Math.DivRem は .NET 6 以降で商と余りをタプルで返すので、分解して受け取れる。
0 以上の余りが欲しいときは、割る数を足してからもう一度 % を取る。
整数を 0 で割ると DivideByZeroException が投げられるが、double の割り算なら例外にはならず、17.0 / 0 は Infinity になる。

つまずき

負の数を割ったときの結果が言語で割れる。
-17 を 5 で割ると、Python の // と % は商 -4・余り 3 を返し、JavaScript・TypeScript・Go・Rust・Java・C# の / と % は商 -3・余り -2 を返す。
前者は商を小さい側へ切り下げ、後者は 0 の側へ切り捨てるためで、どちらでも「商 × 割る数 + 余り = 割られる数」は成り立っている。
配列の添字や曜日の計算のように 0 以上の余りを前提にしたコードを別の言語へ移すと、負の値が来たときだけ結果がずれる。

0 以上の余りの求め方

割る数が正のとき、0 以上の余りは Python ならそのまま %、Java なら Math.floorMod、Rust なら rem_euclid で得られる。
JavaScript・Go・C# では、((a % b) + b) % b のように割る数を足してからもう一度 % を取るか、負になった余りにだけ割る数を足す。
割る数が負になると、Python の 17 % -5 は -3、Java の Math.floorMod(-17, -5) は -2 のように余りが 0 以下になり、この 3 つのうち 0 以上を返したのは Rust の rem_euclid だけだった。

0 で割ったときの振る舞い

整数を 0 で割ったときの振る舞いも揃っていない。
Python は ZeroDivisionError、Java は ArithmeticException、C# は DivideByZeroException を投げ、Go と Rust はパニックで止まる。
JavaScript の number は例外を出さずに Infinity や NaN を返し、その値が後の計算へ静かに流れていく。
割る数が入力や設定値に由来するなら、割る前に 0 かどうかを確かめる。

JavaScript で切り捨てにビット演算を使わない

JavaScript では (a / b) | 0 で小数部を落とす書き方を見かけるが、| は値を 32 ビットの整数に変換してから計算するので、32 ビットに収まらない値では結果が壊れ、2 ** 31 | 0 は -2147483648 になる。
商は Math.trunc で求め、Number.MAX_SAFE_INTEGER を超える整数は BigInt で割る。

この記事は役立ちましたか?