如何为 Kotlin 中的每个数字类型实现 floor 模数?

How to implement floor modulo for every Number type in Kotlin?

我目前正在学习 Kotlin 并尝试创建一个适用于所有 number typesByteLongFloat 等的扩展(中缀)方法.).它应该像 Python 的 % 运算符一样工作:

 4   %   3  ==   1      // only this is the same as Java's %
 4   %  -3  ==  -2
-4   %   3  ==   2
-4   %  -3  ==  -1

...或类似 Java 的 Math.floorMod,但它也应该与 DoubleFloat 一起使用:

-4.3 %  3.2 ==   2.1000000000000005

或这些类型的任何可能组合

 3   %  2.2 ==   0.7999999999999998
 3L  %  2.2f ==   0.7999999999999998

以下按预期工作,但仅适用于两个 Double 或两个 Int:

inline infix fun Double.fmod(other: Double): Number {
    return ((this % other) + other) % other
}

inline infix fun Int.fmod(other: Int): Number {
    return ((this % other) + other) % other
}

// test
fun main(args: Array<String>) {
    println("""
            ${-4.3 fmod 3.2} == 2.1000000000000005

            ${4 fmod 3} == 1
            ${+4 fmod -3} == -2
            ${-4 fmod 3} == 2
            ${-4 fmod -3} == -1
    """)
}

Int 替换为 Number,我收到以下错误消息:

Error:(21, 18) Unresolved reference. None of the following candidates is applicable because of receiver type mismatch: 
@InlineOnly public operator inline fun BigDecimal.mod(other: BigDecimal): BigDecimal defined in kotlin
Error:(21, 27) Public-API inline function cannot access non-public-API 'internal open fun <ERROR FUNCTION>(): [ERROR : <ERROR FUNCTION RETURN TYPE>] defined in root package'
Error:(21, 36) Public-API inline function cannot access non-public-API 'internal open fun <ERROR FUNCTION>(): [ERROR : <ERROR FUNCTION RETURN TYPE>] defined in root package'

如何在不为每种类型组合复制粘贴的情况下为每种数字类型实现这一点?

经过几分钟的摸索,我想出了一个肮脏的方法来做你想做的事:

import java.math.BigDecimal
import java.math.BigInteger

inline infix fun <reified T: Number> T.fmod(other: T): T {
  return when {
    this is BigDecimal || other is BigDecimal -> BigDecimal(other.toString()).let {
      (((BigDecimal(this.toString()) % it) + it) % it) as T
    }
    this is BigInteger || other is BigInteger -> BigInteger(other.toString()).let {
      (((BigInteger(this.toString()) % it) + it) % it) as T
    }
    this is Double || other is Double -> other.toDouble().let {
      (((this.toDouble() % it) + it) % it) as T
    }
    this is Float || other is Float -> other.toFloat().let {
      (((this.toFloat() % it) + it) % it) as T
    }
    this is Long || other is Long -> other.toLong().let {
      (((this.toLong() % it) + it) % it) as T
    }
    this is Int || other is Int -> other.toInt().let {
      (((this.toInt() % it) + it) % it) as T
    }
    this is Short || other is Short -> other.toShort().let {
      (((this.toShort() % it) + it) % it) as T
    }
    else -> throw AssertionError()
  }
}

assert(BigDecimal("2.1") == BigDecimal("-4.3") fmod BigDecimal("3.2"))
assert(BigInteger("2") == BigInteger("-4") fmod BigInteger("3"))
assert(2 == -4 fmod 3)
assert(2L == -4L fmod 3L)

assert(0.7999999999999998 == 3 fmod 2.2)
assert(0.79999995f == 3L fmod 2.2f)

我虽然 reified 不需要转换(智能和显式),但事实并非如此。也许我遗漏了一些东西(毕竟我是 Kotlin 的新手)。

这是一个完全通用的高阶函数方法,没有任何反射或转换:

inline fun <T> T.fmod(other: T, mod: T.(T) -> T, plus: T.(T) -> T) =
     this.mod(other).plus(other).mod(other)

assert(BigDecimal("2.1") == BigDecimal("-4.3").fmod(BigDecimal("3.2"), BigDecimal::mod, BigDecimal::plus))
assert(2L == -4L.fmod(3L, Long::mod, Long::plus))

然而,它并不那么漂亮。

唯一合理的选择(也是最快的)是为您要支持的每对类型定义运算符:

infix fun Double.fmod(other: Double) = ((this % other) + other) % other

infix fun Int.fmod(other: Int) = ((this % other) + other) % other

infix fun Double.fmod(other: Int) = ((this % other) + other) % other

infix fun Int.fmod(other: Double) = ((this % other) + other) % other

这样一来,使用哪种类型是由编译器决定的,而不是在运行时。这些函数不是通用的并且不使用继承(阅读 Number),这意味着值未装箱(请参阅 Java 原始装箱),这意味着对象未分配。

我强烈不建议内联这些函数。将小的优化留给 JVM。未分配对象这一事实是此处最大的性能胜利。

P.S 函数的数量随着支持的类型的平方而增长。您确定需要支持 所有 类型吗?