Operator overloading Kotlin

operator kotlin
method overloading in kotlin
kotlin operator fun invoke
kotlin operator precedence
division operator overloading kotlin
kotlin operator overloading string division
kotlin operator overloading equals
kotlin overload assignment operator

I'm new to kotlin and I'm working on operators overloading for a custom class I defined. The class is called "Rational" and represents a rational number, like for example 117/1098. Class is defined as below and I have overloaded a bunch of operators, like plus, minus, times and so on. However I'm uncertain about what I have to do to overload "in" operator.

Here is my class:

data class Rational(val rational: String) {
    private val numerator: BigInteger
    private val denominator: BigInteger

    init {
        val splitted = rational.split("/")
        numerator = splitted[0].toBigInteger()
        denominator = when (splitted[1]) {
            "0" -> throw Exception("not allowed")
            else -> splitted[1].toBigInteger()
        }
    }

    operator fun plus(number: Rational): Rational {
        val gcm = denominator * number.denominator
        val numerator = (gcm / denominator) * numerator + (gcm / number.denominator) * number.numerator
        return Rational("$numerator/$gcm")
    }

    operator fun minus(number: Rational): Rational {
        val gcm = denominator * number.denominator
        val numerator = (gcm / denominator) * numerator - (gcm / number.denominator) * number.numerator
        return Rational("$numerator/$gcm")
    }

    operator fun times(number: Rational): Rational {
        val numerator = numerator * number.numerator
        val denominator = denominator * number.denominator
        return Rational("$numerator/$denominator")
    }

    operator fun div(number: Rational): Rational {
        val numerator = numerator * number.denominator
        val denominator = denominator * number.numerator
        return Rational("$numerator/$denominator")
    }

    operator fun compareTo(number: Rational): Int {
        val ratio = this.numerator.toFloat() / this.denominator.toFloat()
        val numberRatio = number.numerator.toFloat() / number.denominator.toFloat()
        if (ratio > numberRatio) {
            return 1
        } else if (ratio == numberRatio) {
            return 0
        }
        return -1
    }

    operator fun unaryMinus(): Rational {
        val inverseNumerator = -numerator
        return Rational("$inverseNumerator/$denominator")
    }

    operator fun unaryPlus(): Rational {
        return Rational("$numerator/$denominator")
    }

    operator fun rangeTo(end: Rational): Any {
        var range: MutableList<Rational> = arrayListOf()
        val startNumerator = this.numerator.toInt()
        val endNumerator = end.numerator.toInt()
        var index = 0
        if (this.denominator == end.denominator) {
            for (i in startNumerator..endNumerator) {
                range.add(index, Rational("$i/$denominator"))
            }
        }
        return range
    }

    operator fun contains(number: Rational): Boolean {
        if (this.denominator % number.denominator == 0.toBigInteger()
                && this.numerator <= number.numerator) {
            return true
        }
        return false
    }

    override fun toString(): String {
        val gcd = numerator.gcd(denominator)
        return if (gcd != null) {
            val newNumerator = numerator / gcd
            val newDenominator = denominator / gcd
            "$newNumerator/$newDenominator"
        } else {
            "$numerator/$denominator"
        }
    }
}
infix fun Int.divBy(denominator: Int): Rational {
    if (denominator == 0) {
        throw Exception("denominator 0 not allowed")
    }

    return Rational("$this/$denominator")
}

infix fun Long.divBy(denominator: Long): Rational {
    if (denominator == 0L) {
        throw Exception("denominator 0 not allowed")
    }
    return Rational("$this/$denominator")
}

infix fun BigInteger.divBy(denominator: BigInteger): Rational {
    if (denominator == 0.toBigInteger()) {
        throw Exception("denominator 0 not allowed")
    }
    return Rational("$this/$denominator")
}

fun String.toRational(): Rational {
    return Rational(this)
}

And here is my main body that obviously still doesn't compile:

fun main() {
    val half = 1 divBy 2
    val third = 1 divBy 3
    val twoThirds = 2 divBy 3

    println(half in third..twoThirds) // this line does not compile beacause in operator is not defined for the class
}

I guess I have to override "rangeTo" operator but I'm uncertain about the operator prototype. I there somebody that can please help me to get to the right track?

The way to make in work is for the third..twoThirds call to return something that has a contains(Rational) method, which is what the in call translates to.

One way to do this is to return a ClosedRange<Rational> here, like so:

operator fun rangeTo(end: Rational): ClosedRange<Rational> {
    return object : ClosedRange<Rational> {
        override val endInclusive: Rational = end
        override val start: Rational = this@Rational
    }
}

This puts a type constraint on Rational, as a ClosedRange needs a Comparable implementation to be able to determine whether a value belongs in it. You can do this by implementing the Comparable interface, and then adding operator to your existing compareTo operator (plus it's a good practice to rename the parameter to match the interface):

data class Rational(val rational: String) : Comparable<Rational> {

    ...

    override operator fun compareTo(other: Rational): Int {
        val ratio = this.numerator.toFloat() / this.denominator.toFloat()
        val numberRatio = other.numerator.toFloat() / other.denominator.toFloat()
        if (ratio > numberRatio) {
            return 1
        } else if (ratio == numberRatio) {
            return 0
        }
        return -1
    }

}

You could also avoid the conversion to floats entirely by using this implementation instead, as suggested in the comment below by @gidds:

override operator fun compareTo(other: Rational): Int {
    return (numerator * other.denominator - denominator * other.numerator).signum()
}

Also, your current contains implementation could probably be discarded, as you no longer need it, and it functions rather oddly.


To add something other than the direct answer here: as @Eugene Petrenko suggested in their answer, it would be practical to add a couple constructors other than the one that uses a String, for example one that takes two Ints, and one that takes two BigIntegerss.

Operator Overloading in Kotlin, Overloaded operators are not always commutative. That is, we can't swap the operands and expect things to work as smooth as possible. Operator Overloading in Kotlin 1. Overview. In this tutorial, we're going to talk about the conventions that Kotlin provides to support operator 2. The operator Keyword. In Java, operators are tied to specific Java types. For example, String and numeric types in 3. Overloading for Unary

The in operator is declared inverse. You need an extension function on the right side that takes the left side.

https://kotlinlang.org/docs/reference/operator-overloading.html#in

You miss an infix function divBy to allow turing Int into Rational, e.g.

infix fun Int.divBy(i: Int) = Rational("$this/$i")

Not the code like val half = 1 divBy 2 will work. Theoretically, it may make sense to add a constructor for Rational from Ints to avoid parsing.

There is an incorrect return type in rangeTo method in the Rational class, it should not be Any. It should be declared as

data class RationalRange(val left: Rational, val right: Rational) {
  operator fun contains(r: Rational) = left <= r && r <= right
}

operator fun rangeTo(end: Rational): RationalRange(this, end) 

Now the example with x in a..b should work.

UPD: added the RationalRange. I missed the point, sorry. You do not need contains function implemented for the Rational class at all.

The compareTo function of Rational is unlikely to use .toFloat() instead, you may implement that directly with integer numbers

Kotlin Operator Overloading (With Examples), In this article, you will learn about operator overloading (define how operator works for user defined types like objects) with the help of examples. When you use� Operator overloading Kotlin allows us to overload some operators on any object we have created, or that we know of (through extensions). The concept of operator overloading provides a way to invoke functions to perform arithmetic operation, equality checks or comparison on whatever object we want, through symbols like +, -, /, *, %, <, >.

A straightforward solution is to implement the Comparable Interface in your class.

data class Rational(val rational: String) : Comparable<Rational>

Then implement the compareTo() function, with your comparison logic.

override fun compareTo(other: Rational): Int {
    //Normalize the numerators of each rational
    val thisNumerator = this.numerator * other.denominator
    val otherNumerator = other.numerator * this.denominator
    //Then compare them
    return when{
        thisNumerator > otherNumerator -> 1
        thisNumerator < otherNumerator -> -1
        else -> 0
    }
}

This will resolve the compile error without you needing to override the rangeTo() function with custom logic.

Kotlin Operator Overloading, Kotlin Operator Overloading. Since Kotlin provides user-defined types, it also provides the additional functionality to overload the standard operators, so that� We looked at what operator overloading is and how to do it in Kotlin. We see that the operators serve as a shorthand for function calls, and consequently anything written with an operator can be written explicitly with the corresponding function.

Operator overloading in Kotlin, Kotlin has a fixed number or symbolic operators we can easily use on any class. The way is to create a function with a reserved name that will be mapped to the� Kotlin supports a technique called conventions, everyone should be familiar with. For example, if you define a special method plus in your class, you can use the + operator by convention: Kotlin Operator Overloading.

[Kotlin] Operator overloading, Operator overloading. Kotlin allows us to overload some operators on any object we have created, or that we know of (through extensions). The concept of� And because there is no way to define a class of a function type in kotlin then implement operator overloading as a member function of a function. – Sero Mar 31 '17 at 22:50 1 For functional types yes, but any class that has an open invoke function can be overridden.

When exactly is the operator keyword required in Kotlin?, The operator overloading docs linked to from the exercise explicitly says: Functions that overload operators need to be marked with the operator� operator marks a function as overloading an operator or implementing a convention out marks a type parameter as covariant override marks a member as an override of a superclass member private marks a declaration as visible in the current class or file

Comments
  • As a matter of design, I'd suggest adding a constructor that takes a pair of BigIntegers. You can then do the GCD calculation there, instead of in toString(). Avoiding all the conversion of BigIntegers to Stringss and back again should save memory and time, as well as simplifying the code a bit. You'll probably also want to override equals() and hashCode(), along with operators inc(), and dec(). If you override toByte() &c, you can implement Number too. And why define compareTo() without implementing Comparable? — Yes, I've implemented a Rational class too :-)
  • compareTo() doesn't need to do any division or conversions at all: you could just do e.g. (numerator * other.denominator - denominator * other.numerator).sign.
  • Oops, of course! I'll add this to the answer itself as well, thank you!
  • Thanks for all your help :) everything works fine now!
  • rangeTo should most definitely not return a Rational, it makes no sense to then check if a Rational contains another.
  • @Eugene, thanks a lot. Makes more sense know. I have indeed defined infix functions but I forgot to cut & paste them, going to update the question
  • Thanks for all your help :) everything works fine now!