如何在Kotlin中为每个数字types实现地板模数?

我目前正在学习Kotlin,并试图创建一个适用于所有数字types ( ByteLongFloat等)的扩展(infix)方法。 它应该像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 

或者这些types的任何可能的组合

  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) { println(""" ${-4.3 fmod 3.2} == 2.1000000000000005 ${4 fmod 3} == 1 ${+4 fmod -3} == -2 ${-4 fmod 3} == 2 ${-4 fmod -3} == -1 """) } 

Number替换Int ,我得到以下错误信息:

 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 : ] defined in root package' Error:(21, 36) Public-API inline function cannot access non-public-API 'internal open fun (): [ERROR : ] defined in root package' 

我怎么能达到这个每个数字types没有复制粘贴这个每个types的组合?

唯一合理的选择(也是最快的)是为你想要支持的每对types定义操作符:

 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 

这样,编译器决定使用什么types,而不是在运行时。 这些函数不是通用的,也不使用inheritance(read Number ),这意味着这些值不是装箱的(请参阅Java原始装箱),这意味着不分配对象。

我强烈建议不要内联这些function。 对JVM留下较小的优化。 对象不分配的事实是这里最大的表现。

PS函数的数量增长为支持的types的平方。 你确定你需要支持所有types?

几分钟后,我想出了一个肮脏的方法来做你想做的事情:

 import java.math.BigDecimal import java.math.BigInteger inline infix fun  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) 

我虽然通过将使铸造(聪明和明确)是不必要的,但事实并非如此。 也许我错过了一些东西(毕竟我是Kotlin的新手)。

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

 inline fun  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)) 

但是,它并不漂亮。

Interesting Posts