如何在Kotlin中为每个Number类型实现floor modulo?

时间:2016-06-27 10:48:13

标签: kotlin modulo

我目前正在学习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'

如何针对每种类型组合进行复制粘贴,如何为每种数字类型实现此目的?

3 个答案:

答案 0 :(得分:3)

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

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

这样决定使用什么类型的编译器,而不是运行时。这些函数不是通用的,不使用继承(read Number),这意味着值不是盒装(参见Java原始装箱),这意味着没有分配对象。

我强烈建议不要内联这些功能。将少量优化留给JVM。没有分配对象的事实是这里最大的表现。

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

答案 1 :(得分:1)

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

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的新手)。

答案 2 :(得分:0)

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

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

然而,它并不那么漂亮。