偶然发现一个有意思的问题:负数如何取模?
乍一看: 这不是很简单?
但仔细一想,我觉得很简单的只是正数准确的说是正整数的取模。
也就是 7 % 3 = 1
那 (-7) % 3 和 7 % (-3) 以及 (-7) % (-3) 呢?
仔细一想,不会!
经过一番摸索之后发现:
In mathematics, the result of the modulo operation is an equivalence class, and any member of the class may be chosen as representative; however, the usual representative is the least positive residue, the smallest non-negative integer that belongs to that class (i.e., the remainder of the Euclidean division). However, other conventions are possible. Computers and calculators have various ways of storing and representing numbers; thus their definition of the modulo operation depends on the programming language or the underlying hardware.
what?
也就是说,模的取值还不止一种可能,一般是最小的正余数。
在几乎所有的编程语言中, 的商 和余数 满足如下定义:
其中, 是整数集合。
发现了吗?这个定义是有歧义的:
- 如果余数为 0,自然皆大欢喜
- 如果余数非 0,那么就有两种选择,正余数和负余数
这在被除数和除数都是正数时还不明显,一般没有人会强行将 的吧?
其实余数的关键在于商的选取,对于商 的选取,不同的编程语言实现不一致,主要有以下几种:
- floored division,就是商采用向下取整,趋负无穷截尾
其中, 表示向下取整,因此
余数符号与除数 相同
- truncated division,就是商尽可能的靠近 0,因此又称截断取整
其中, 是截断函数。因此,
余数符号与被除数 相同。
- Euclidean division,就是欧几里得除法
其中, 是向上取整 是取符号函数, 因此,
- 其他的像 rounded division(四舍五入) 和 ceiling division (向上取整)可以在这里了解
比如在 Rust 中,对于 % 就是采用的 truncated division
因此,在 Rust 中,
(-7) % 3 就是 ,
7 % (-3) 就是
当然,在 Rust 中,也有 Euclidean division 的求模方法:rem_euclid()
(-7i32).rem_euclid(3) 就是
7i32.rem_euclid(-3) 就是
在 JavaScript 中,则只有 % 一种求模方法,跟 C++ 一样是 truncated division
在 Haskell 中,
mod 是采用 floored division
(-7) `mod` 3 -- 2
7 `mod` (-3) -- -2
rem是采用 truncated division
(-7) `rem` 3 -- -1
7 `rem` (-3) -- 1
总结
主流编程语言中,除法主要有三种方式取余数,floored division,truncated division 以及 Euclidean division。不同的实现方式可能会产生不同的结果,在进行负数的模运算时尤其要注意。