From f221a71c0d105edf2a3ecf520d127407584dd12a Mon Sep 17 00:00:00 2001 From: Michael Abbott <32575566+mcabbott@users.noreply.github.com> Date: Sat, 13 Jul 2019 10:46:41 +0200 Subject: [PATCH 1/7] mod(n, range) --- base/range.jl | 7 +++++++ test/ranges.jl | 16 ++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/base/range.jl b/base/range.jl index 66d4595fb6466..a12662f5d3ecc 100644 --- a/base/range.jl +++ b/base/range.jl @@ -1038,3 +1038,10 @@ function +(r1::StepRangeLen{T,S}, r2::StepRangeLen{T,S}) where {T,S} end -(r1::StepRangeLen, r2::StepRangeLen) = +(r1, -r2) + +# Modular arithmetic on ranges + +mod(i::Integer, r::OneTo) = mod1(i, last(r)) +mod(i::Integer, r::AbstractUnitRange) = mod(i-first(r), length(r)) + first(r) +mod(i::Integer, r::StepRange) = abs(r.step)==1 ? mod(i-minimum(r), length(r)) + minimum(r) : + throw(ArgumentError("step must be +1 or -1")) diff --git a/test/ranges.jl b/test/ranges.jl index 99f69658d3a77..60d71dd87548d 100644 --- a/test/ranges.jl +++ b/test/ranges.jl @@ -1519,3 +1519,19 @@ 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, OneTo(5)) == mod1(n, 5) + @test mod(n, reverse(0:4)) == mod(n, 5) + @test mod(n, reverse(2:6)) == 2 + mod(n-2, 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 ArgumentError mod(3, 1:2:7) + @test_throws DivideError mod(3, 1:0) +end From b05fd93fa2219b9fceaa57797ba4cea4106ac5b6 Mon Sep 17 00:00:00 2001 From: Michael Abbott <32575566+mcabbott@users.noreply.github.com> Date: Mon, 22 Jul 2019 14:23:40 +0200 Subject: [PATCH 2/7] fix a test --- test/ranges.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ranges.jl b/test/ranges.jl index 60d71dd87548d..dce6a7e448d19 100644 --- a/test/ranges.jl +++ b/test/ranges.jl @@ -1525,7 +1525,7 @@ end @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, OneTo(5)) == mod1(n, 5) + @test mod(n, Base.OneTo(5)) == mod1(n, 5) @test mod(n, reverse(0:4)) == mod(n, 5) @test mod(n, reverse(2:6)) == 2 + mod(n-2, 5) end From 4596139cb5a8880d9ff9f152e9a11107123d63ef Mon Sep 17 00:00:00 2001 From: Michael Abbott <32575566+mcabbott@users.noreply.github.com> Date: Mon, 22 Jul 2019 20:00:42 +0200 Subject: [PATCH 3/7] avoid floats --- base/range.jl | 2 +- test/ranges.jl | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/base/range.jl b/base/range.jl index a12662f5d3ecc..2812299cc1be1 100644 --- a/base/range.jl +++ b/base/range.jl @@ -1042,6 +1042,6 @@ end # Modular arithmetic on ranges mod(i::Integer, r::OneTo) = mod1(i, last(r)) -mod(i::Integer, r::AbstractUnitRange) = mod(i-first(r), length(r)) + first(r) +mod(i::Integer, r::AbstractUnitRange{<:Integer}) = mod(i-first(r), length(r)) + first(r) mod(i::Integer, r::StepRange) = abs(r.step)==1 ? mod(i-minimum(r), length(r)) + minimum(r) : throw(ArgumentError("step must be +1 or -1")) diff --git a/test/ranges.jl b/test/ranges.jl index dce6a7e448d19..976faa2c13cc2 100644 --- a/test/ranges.jl +++ b/test/ranges.jl @@ -1532,6 +1532,7 @@ 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 ArgumentError mod(3, 1:2:7) @test_throws DivideError mod(3, 1:0) end From 8b50d36f1866efa8699333137ac92d7e180d257a Mon Sep 17 00:00:00 2001 From: Michael Abbott <32575566+mcabbott@users.noreply.github.com> Date: Mon, 22 Jul 2019 20:04:26 +0200 Subject: [PATCH 4/7] news + docstring --- NEWS.md | 3 ++- base/range.jl | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 674d1090fa31b..5e92560d301e7 100644 --- a/NEWS.md +++ b/NEWS.md @@ -40,7 +40,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 range as the second argument ([#32628]). #### Libdl diff --git a/base/range.jl b/base/range.jl index 2812299cc1be1..9fdff1a37e425 100644 --- a/base/range.jl +++ b/base/range.jl @@ -1041,6 +1041,23 @@ end # Modular arithmetic on ranges +""" + mod(x::Integer, range) + +Find `y` in `range` such that `x` ≡ `y` (mod `n`), where `n = length(range)`. +The range must contain only integers, and have step +1 or -1. + +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) mod(i::Integer, r::StepRange) = abs(r.step)==1 ? mod(i-minimum(r), length(r)) + minimum(r) : From 85ad76a1e72ebb3138d2a09380584d399abfcf90 Mon Sep 17 00:00:00 2001 From: Michael Abbott <32575566+mcabbott@users.noreply.github.com> Date: Wed, 24 Jul 2019 08:35:05 +0200 Subject: [PATCH 5/7] step +1 only --- base/range.jl | 7 ++----- test/ranges.jl | 4 +--- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/base/range.jl b/base/range.jl index 9fdff1a37e425..8116462d54c55 100644 --- a/base/range.jl +++ b/base/range.jl @@ -1042,10 +1042,9 @@ end # Modular arithmetic on ranges """ - mod(x::Integer, range) + mod(x::Integer, r::AbstractUnitRange) -Find `y` in `range` such that `x` ≡ `y` (mod `n`), where `n = length(range)`. -The range must contain only integers, and have step +1 or -1. +Find `y` in `r` such that `x` ≡ `y` (mod `n`), where `n = length(r)`. See also: [`mod1`](@ref). @@ -1060,5 +1059,3 @@ julia> mod(3, 0:2) """ mod(i::Integer, r::OneTo) = mod1(i, last(r)) mod(i::Integer, r::AbstractUnitRange{<:Integer}) = mod(i-first(r), length(r)) + first(r) -mod(i::Integer, r::StepRange) = abs(r.step)==1 ? mod(i-minimum(r), length(r)) + minimum(r) : - throw(ArgumentError("step must be +1 or -1")) diff --git a/test/ranges.jl b/test/ranges.jl index 976faa2c13cc2..b8c6c33d20cdf 100644 --- a/test/ranges.jl +++ b/test/ranges.jl @@ -1526,13 +1526,11 @@ end @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) - @test mod(n, reverse(0:4)) == mod(n, 5) - @test mod(n, reverse(2:6)) == 2 + mod(n-2, 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 ArgumentError mod(3, 1:2:7) + @test_throws MethodError mod(3, 1:2:7) @test_throws DivideError mod(3, 1:0) end From f71867ecd7f7609c151d95638d20aea02b4d9d3d Mon Sep 17 00:00:00 2001 From: Michael Abbott <32575566+mcabbott@users.noreply.github.com> Date: Wed, 24 Jul 2019 08:38:01 +0200 Subject: [PATCH 6/7] more detailed NEWS.md Co-Authored-By: Matt Bauman --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 5e92560d301e7..06a233b756e36 100644 --- a/NEWS.md +++ b/NEWS.md @@ -41,7 +41,7 @@ Standard library changes * `IPAddr` subtypes now behave like scalars when used in broadcasting ([#32133]). * `clamp` can now handle missing values ([#31066]). * `empty` now accepts a `NamedTuple` ([#32534]). -* `mod` now accepts a range as the second argument ([#32628]). +* `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 From f93604aa24ade37fa4ca7a1c0446894dd27543ee Mon Sep 17 00:00:00 2001 From: Michael Abbott <32575566+mcabbott@users.noreply.github.com> Date: Wed, 24 Jul 2019 08:50:43 +0200 Subject: [PATCH 7/7] docstring++ --- base/range.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/base/range.jl b/base/range.jl index 8116462d54c55..12c8a729a518e 100644 --- a/base/range.jl +++ b/base/range.jl @@ -1044,7 +1044,8 @@ end """ mod(x::Integer, r::AbstractUnitRange) -Find `y` in `r` such that `x` ≡ `y` (mod `n`), where `n = length(r)`. +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).