Skip to content

Commit 6153a7e

Browse files
sostockKristofferC
authored andcommitted
Fix length(::AbstractUnitRange) and speed up length(::AbstractUnitRange{<:Rational}) (#41479)
* Fix length(::AbstractUnitRange), faster length(::AbstractUnitRange{<:Rational}) (cherry picked from commit 1c951f7)
1 parent dd697ec commit 6153a7e

File tree

3 files changed

+40
-10
lines changed

3 files changed

+40
-10
lines changed

base/range.jl

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -663,17 +663,17 @@ function checked_length(r::OrdinalRange{T}) where T
663663
else
664664
diff = checked_sub(stop, start)
665665
end
666-
a = Integer(div(diff, s))
667-
return checked_add(a, oneunit(a))
666+
a = div(diff, s)
667+
return Integer(checked_add(a, oneunit(a)))
668668
end
669669

670670
function checked_length(r::AbstractUnitRange{T}) where T
671671
# compiler optimization: remove dead cases from above
672672
if isempty(r)
673673
return Integer(first(r) - first(r))
674674
end
675-
a = Integer(checked_add(checked_sub(last(r), first(r))))
676-
return checked_add(a, oneunit(a))
675+
a = checked_sub(last(r), first(r))
676+
return Integer(checked_add(a, oneunit(a)))
677677
end
678678

679679
function length(r::OrdinalRange{T}) where T
@@ -690,15 +690,14 @@ function length(r::OrdinalRange{T}) where T
690690
else
691691
diff = stop - start
692692
end
693-
a = Integer(div(diff, s))
694-
return a + oneunit(a)
693+
a = div(diff, s)
694+
return Integer(a + oneunit(a))
695695
end
696696

697-
698697
function length(r::AbstractUnitRange{T}) where T
699698
@_inline_meta
700-
a = Integer(last(r) - first(r)) # even when isempty, by construction (with overflow)
701-
return a + oneunit(a)
699+
a = last(r) - first(r) # even when isempty, by construction (with overflow)
700+
return Integer(a + oneunit(a))
702701
end
703702

704703
length(r::OneTo) = Integer(r.stop - zero(r.stop))

base/rational.jl

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,3 +533,21 @@ function hash(x::Rational{<:BitInteger64}, h::UInt)
533533
h = hash_integer(num, h)
534534
return h
535535
end
536+
537+
# These methods are only needed for performance. Since `first(r)` and `last(r)` have the
538+
# same denominator (because their difference is an integer), `length(r)` can be calulated
539+
# without calling `gcd`.
540+
function length(r::AbstractUnitRange{T}) where T<:Rational
541+
@_inline_meta
542+
f = first(r)
543+
l = last(r)
544+
return div(l.num - f.num + f.den, f.den)
545+
end
546+
function checked_length(r::AbstractUnitRange{T}) where T<:Rational
547+
f = first(r)
548+
l = last(r)
549+
if isempty(r)
550+
return f.num - f.num
551+
end
552+
return div(checked_add(checked_sub(l.num, f.num), f.den), f.den)
553+
end

test/ranges.jl

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,19 @@ end
544544
end
545545
end
546546

547+
# A number type with the overflow behavior of `UInt8`. Conversion to `Integer` returns an
548+
# `Int32`, i.e., a type with different `typemin`/`typemax`. See #41479
549+
struct OverflowingReal <: Real
550+
val::UInt8
551+
end
552+
OverflowingReal(x::OverflowingReal) = x
553+
Base.:<=(x::OverflowingReal, y::OverflowingReal) = x.val <= y.val
554+
Base.:+(x::OverflowingReal, y::OverflowingReal) = OverflowingReal(x.val + y.val)
555+
Base.:-(x::OverflowingReal, y::OverflowingReal) = OverflowingReal(x.val - y.val)
556+
Base.round(x::OverflowingReal, ::RoundingMode) = x
557+
Base.Integer(x::OverflowingReal) = Int32(x.val)
558+
@test length(OverflowingReal(1):OverflowingReal(0)) == 0
559+
547560
@testset "loops involving typemin/typemax" begin
548561
n = 0
549562
s = 0
@@ -1095,7 +1108,7 @@ end
10951108
@testset "issue 10950" begin
10961109
r = 1//2:3
10971110
@test length(r) == 3
1098-
@test_throws MethodError checked_length(r) == 3 # this would work if checked_sub is defined on Rational
1111+
@test checked_length(r) == 3
10991112
i = 1
11001113
for x in r
11011114
@test x == i//2

0 commit comments

Comments
 (0)