Should inv(::Rational) make copies of the numerator and denominator?

Usually operations on Rational numbers don’t return references to their arguments:

julia> d = Rational{BigInt}(2)
2//1

julia> d.num === (1*d).num
false

julia> d.num === (0+d).num
false

I assumed that this holds in general, but I was wrong:

julia> d.num === inv(d).den
true

It turns out that inv(::Rational) returns the exact same numerator and denominator objects, just switched around in the Rational object: julia/rational.jl at master · JuliaLang/julia · GitHub

Sometimes this distinctions is irrelevant, but this behavior was quite a head-scratcher for sure while writing some code making use of MutableArithmetics.jl. An example of unexpected behavior:

julia> a = Rational{BigInt}(2)
2//1

julia> b = inv(a)
1//2

julia> using MutableArithmetics

julia> zero!!(a)
0//1

julia> b  # mutating a also mutates b!
1//0

Perhaps inv should be changed so as to copy the numerator and denominator, instead of returning references to the input value, for consistency with the other operations? I know this would cause some pain for more basic use-cases, though…

Maybe it would be best to keep the current behavior, but document it somehow?

2 Likes

Numbers should behave immutably. If you’re going to mutate them then that’s on you and you should make sure no one can observe you doing it.

6 Likes

If I understand you correctly, you’re saying that users of MutableArithmetics should be aware of the implementation of Base and the standard library?

This doesn’t sound so bad until one needs to upgrade to a new Julia release and some of the implementation of Julia changes, requiring changes to my code… If I got this right, it basically means that MutableArithmetics is only good for one-off experimentation, which would be quite a shame for applications that need the performance offered by MutableArithmetics.

Someone who really wants the performance of MutableArithmetics should be grateful inv(::Rational) doesn’t allocate by default.