我目前正在学习Kotlin并尝试创建适用于所有number types(Byte
,Long
,Float
等的扩展(中缀)方法)。它应该像Python的%
运算符一样工作:
4 % 3 == 1 // only this is the same as Java's %
4 % -3 == -2
-4 % 3 == 2
-4 % -3 == -1
...或者像Java的Math.floorMod
一样,但它也适用于Double
或Float
:
-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'
如何针对每种类型组合进行复制粘贴,如何为每种数字类型实现此目的?
答案 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))
然而,它并不那么漂亮。