Abstract operator methods for type Number

I have a class similar to the follwing:

class NumberCruncher<T: Number>(val v: T) {
    fun crunch1(v2: T) = v + v2
    fun crunch2(v2: Double) = v + v2
}

The compiler produces an error for each method, because the operator plus method is not declared for Number. How about declaring the operators as abstract methods?

I don’t know what was the reason to not include this in the first place, but please remember that Kotlin intentionally doesn’t support math operations for inconsistent number types. You can’t add numbers to numbers, but integers to integers, doubles to doubles, etc.

I see a lot of questions about this — it’s clearly a source of some confusion.

Part of the problem is that it seems like Number should be such a powerful abstraction. Write the code just once, and it can work for all numeric types! No need to force your choice of Int or Double on everyone (or write umpteen versions of the same code)!

Unfortunately, there are two major problems: one fundamental, one pragmatic.

The fundamental one is that of the result types. If you add, say, a Float and an Int, what type should the result be? There’s no natural answer; whatever you choose, it may not be able to hold the result exactly. And the same problem recurs for all combinations of numeric types, and for most binary operations. (And it gets even harder once you bring in the unsigned types.)

And even if you somehow hard-code the results for all the primitive numeric types, don’t forget that there’s also BigInteger, and BigDecimal, and any other subclasses of Number that you write yourself or find in third-party libraries…

Of course, that’s not a problem for operations upon two numbers of the same type. (Or at least, no more of a problem than we already have.) But that would be a bit restrictive.

The pragmatic problem is that java.lang.Number (to which kotlin.Number is mapped) doesn’t support anything except for a few conversion methods. So it’s not easy for Kotlin to add them. (Again, even if it added lots of extension methods, they wouldn’t be able to cope with new Number subclasses.)

This is all very unfortunate. If java.lang.Number had been designed a bit more imaginatively (e.g. with a type parameter giving the concrete type, and lots more abstract methods), then it could have been far more powerful. But I don’t think it’s possible for Kotlin to retrofit any of that, so we’re pretty much stuck with it.

(Of course, if anyone has any bright ideas, I’d be interested to see them!)

Interesting take. Just thought I’d mention my suspicion that similar requests for Number might often violate LSP.

For example, people are often tempted to make a Square a sub-type of Rectangle and LSP shows that is incorrect (except when immutable).

Does anybody know of any arithmetic that could be added to Number without violating LSP? Maybe non-arithmetic additions to Number could make sense but I’m having trouble coming up with one example of a valid arithmetic method that would fit for Number without something else major being changed to the language (ex. Self-types).

1 Like

You could technically do something like this:

import java.math.*
operator fun Number.plus(other: Number): Number = toBigDecimal().add(other.toBigDecimal())

fun Number.toBigDecimal(context: MathContext = MathContext.UNLIMITED) = BigDecimal(this.toString(), context)

fun main() {
  println((0.1 as Number) + 0.2)
}

I think with the right conversion afterwards this might not violate LSP. However, maybe due to the unlimited precision used we’re technically not doing the same rounding errors that would happen automatically, but I think something like BigDecimal probably matches the developers intent more since I doubt? that anyone relies on rounding errors.

1 Like

Ah yes! I guess the only caveat is it’s assumed all implementations if Number can be parsed into a BigDecimal through their toString().

Maybe a better request for OP is to have a toBigDecimal() conversation added to number, or alternatively an unlimited precision number type be added to Kotlin

Edit: And of course the caveat that the return type for any common operation with a Number would result in that unlimited precision type.

1 Like

That might just about work (though in a very wasteful way) for the built-in numeric classes. But it can never be a full solution, because anyone can write an implementation of Number, working in any way they like!

For example, suppose someone writes an implementation that stores numbers as arbitrary-precision floating-point fractions in base 3 (ternary). Neither a decimal type nor a binary type could store even the simplest ternary fraction exactly (1/3 = 0.1₃ ≈ 0.33333333…₁₀ ≈ 0.01010101…₂). And of course similar applies to an infinite number of other possible number bases.

So no, there can never be an ‘über’ numeric type that can act as a superset of all the others, even with unlimited precision.

(That’s why a full-featured Number type would be so useful, of course…)

Good point.

It has me considering what a Number type is in kotlin. and now I’d describe it as: “A number type is a type that can be converted (not perfectly) into an int, double, float, … etc”

I guess it you’d need a number type that can be converted to perfectly from all others to make any sense of it.

Also, here’s the link to more discussion Kotlin Number class mystery - #8 by akurczak

I suspect this thread isn’t the last in the chain either ;D

1 Like