Sum of rationals

I wrote the following function to calculate the sum of a vector of rational numbers:

function rationalsum(numbers::Vector{T} where T<:Rational)
    den = lcm([x.den for x in numbers])
    nums = (x.num * div(den, x.den) for x in numbers)
    return sum(nums)//den
end

In my benchmarks it is 4-8 times faster than sum() (depending on the rational numbers). My first assumption was that this is because the sum() method looks for the common denominator at every step. But the performance ratio between the two appears to be close to constant, so this is likely not the explanation. Is my implementation bad or missing something?

Your function doesn’t check for overflow, where most/all(?) operations on Rationals are checked:

julia> rats = [(typemax(Int64)-1)//typemax(Int64), (typemax(Int64)-2)//typemax(Int64), (typemax(Int64)-1)//typemax(Int64)]
3-element Vector{Rational{Int64}}:
 9223372036854775806//9223372036854775807
 9223372036854775805//9223372036854775807
 9223372036854775806//9223372036854775807

julia> sum(rats)
ERROR: OverflowError: 9223372036854775806 + 9223372036854775805 overflowed for type Int64
Stacktrace:
  [1] throw_overflowerr_binaryop(op::Symbol, x::Int64, y::Int64)
    @ Base.Checked ./checked.jl:155
  [2] checked_add
    @ ./checked.jl:167 [inlined]
  [3] +
    @ ./rational.jl:290 [inlined]
  [4] add_sum
    @ ./reduce.jl:27 [inlined]
  [5] _mapreduce(f::typeof(identity), op::typeof(Base.add_sum), #unused#::IndexLinear, 
...
 [14] sum(a::Vector{Rational{Int64}})
    @ Base ./reducedim.jl:994
 [15] top-level scope
    @ REPL[8]:1

julia> function rationalsum(numbers::Vector{T} where T<:Rational)
           den = lcm([x.den for x in numbers])
           nums = (x.num * div(den, x.den) for x in numbers)
           return sum(nums)//den
       end
rationalsum (generic function with 1 method)

julia> rationalsum(rats)
9223372036854775801//9223372036854775807
4 Likes