kotlin 取余

Posted on By ᵇᵒ

众所周知,kotlin 里取余有两种方式 %mod。一直以来我以为两者是等价的,甚至想当然认为 mod% 的操作符重载函数,直到最近刷 LeetCode 碰上一个 bug,深入了解之下才发现并不一样。
不借助任何搜索看看你能否答对如下输出:

fun main() {
    println("5 % 4 = ${5 % 4}")
    println("5.mod(4) = ${5.mod(4)}")

    println("-5 % 4 = ${-5 % 4}")
    println("-5.mod(4) = ${(-5).mod(4)}")

    println("5 % -4 = ${5 % -4}")
    println("5.mod(-4) = ${5.mod(-4)}")

    println("-5 % -4 = ${-5 % -4}")
    println("-5.mod(-4) = ${(-5).mod(-4)}")
}

结果如下:

5 % 4 = 1
5.mod(4) = 1

-5 % 4 = -1
-5.mod(4) = 3

5 % -4 = 1
5.mod(-4) = -3

-5 % -4 = -1
-5.mod(-4) = -1

当被除数和除数符号位一致时,%mod 结果相同;然而符号位不一致时,结果大相庭径。

%

  • % 使用的是向 0 截断除法(truncate toward zero),即 r = a - (a / b) * b
  • % 取余的结果符号位跟 dividend(被除数) 相同

BTW,% 对应的操作符重载函数是 rem

class Foo(private val value: Int) {
    operator fun rem(other: Foo): Foo {
        return Foo(value % other.value)
    }
}

mod

源码在 FloorDivMod.kt 文件,以整数为例:

/**
 * Calculates the remainder of flooring division of this value (dividend) by the other value (divisor).
 *
 * The result is either zero or has the same sign as the _divisor_ and has the absolute value less than the absolute value of the divisor.
 */
@SinceKotlin("1.5")
@kotlin.internal.InlineOnly
@kotlin.internal.IntrinsicConstEvaluation
public inline fun Int.mod(other: Int): Int {
    val r = this % other
    return r + (other and (((r xor other) and (r or -r)) shr 31))
}

return r + (other and (((r xor other) and (r or -r)) shr 31)) 意思是如果 r 和 other 符号不同,就加上 other。等价于:

public inline fun Int.mod(other: Int): Int {
    val r = this % other
    return if ((r >= 0 && other > 0) || (r <= 0 && other < 0)) r else r + other
}
  • 由文件名不难看出 mod 是向下取整(floor division),也即 r = a - floor(a.toDouble() / b).toInt() * b
  • mod 取余的结果符号位跟 divisor(除数) 相同(余数 0 除外)

总结

%mod 区别省流版:

  1. % 的实现使用的是向 0 截断除法;mod 使用的是向下取整除法
  2. % 取余结果的符号位与被除数相同;mod 结果符号位与除数相同(余数 0 除外)