diff --git a/NEWS.md b/NEWS.md index cad094692e412..f663bbc5c0ea7 100644 --- a/NEWS.md +++ b/NEWS.md @@ -47,7 +47,8 @@ Standard library changes when operating over zero-dimensional arrays ([#32122]). * `IPAddr` subtypes now behave like scalars when used in broadcasting ([#32133]). * `clamp` can now handle missing values ([#31066]). -* `empty` now accepts a `NamedTuple` ([#32534]) +* `empty` now accepts a `NamedTuple` ([#32534]). +* `mod` now accepts a unit range as the second argument to easily perform offset modular arithmetic to ensure the result is inside the range ([#32628]). #### Libdl diff --git a/base/range.jl b/base/range.jl index 66d4595fb6466..12c8a729a518e 100644 --- a/base/range.jl +++ b/base/range.jl @@ -1038,3 +1038,25 @@ function +(r1::StepRangeLen{T,S}, r2::StepRangeLen{T,S}) where {T,S} end -(r1::StepRangeLen, r2::StepRangeLen) = +(r1, -r2) + +# Modular arithmetic on ranges + +""" + mod(x::Integer, r::AbstractUnitRange) + +Find `y` in the range `r` such that ``x ≡ y (mod n)``, where `n = length(r)`, +i.e. `y = mod(x - first(r), n) + first(r)`. + +See also: [`mod1`](@ref). + +# Examples +```jldoctest +julia> mod(0, Base.OneTo(3)) +3 + +julia> mod(3, 0:2) +0 +``` +""" +mod(i::Integer, r::OneTo) = mod1(i, last(r)) +mod(i::Integer, r::AbstractUnitRange{<:Integer}) = mod(i-first(r), length(r)) + first(r) diff --git a/test/ranges.jl b/test/ranges.jl index 99f69658d3a77..b8c6c33d20cdf 100644 --- a/test/ranges.jl +++ b/test/ranges.jl @@ -1519,3 +1519,18 @@ end @test range(1, step = big(1.0), length=10) == big(1.0):1:10 @test range(1.0, step = big(1.0), length=10) == big(1.0):1:10 end + +@testset "mod with ranges" begin + for n in -10:10 + @test mod(n, 0:4) == mod(n, 5) + @test mod(n, 1:5) == mod1(n, 5) + @test mod(n, 2:6) == 2 + mod(n-2, 5) + @test mod(n, Base.OneTo(5)) == mod1(n, 5) + end + @test mod(Int32(3), 1:5) == 3 + @test mod(big(typemax(Int))+99, 0:4) == mod(big(typemax(Int))+99, 5) + @test_throws MethodError mod(3.141, 1:5) + @test_throws MethodError mod(3, UnitRange(1.0,5.0)) + @test_throws MethodError mod(3, 1:2:7) + @test_throws DivideError mod(3, 1:0) +end