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

匿名 (未验证) 提交于 2019-12-03 09:14:57

问题:

I'm currently learning Kotlin and trying to create an extension (infix) method that works on all number types (Byte, Long, Float, etc.). It should work like Python's % operator:

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

...or like Java's Math.floorMod, but it should also work with Double or Float:

-4.3 %  3.2 ==   2.1000000000000005 

or with any possible combination of these types

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

The following works as intended, but only for two Double or two 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     """) } 

Replacing Int with Number, I get the following error messages:

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' 

How can I achieve this for every number type without copy-pasting this for every type combination?

回答1:

The only reasonable option (also the fastest) is to define the operator for each pair of types you want to support:

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 

That way the decision of what type to use is made by the compiler, not at the runtime. Those functions are not generic and do not use inheritance (read Number), which means that the values are not boxed (see Java primitive boxing), meaning that objects are not allocated.

I strongly do not recommend inlining those functions. Leave minor optimizations to JVM. The fact that objects are not allocated is the biggest performance win here.

P.S The number of functions grows as a square of types supported. Are you sure you need to support all types?



回答2:

After several minutes toying around, I came up with a dirty approach to do what you want:

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) 

I though reified would make casting (smart and explicit) unnecessary but it was not the case. Maybe I'm missing something (I'm new to Kotlin after all).



回答3:

Here's a completely generic higher-order-function approach without any reflection or casting:

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

However, it's not so pretty.



标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!