mod(::Complex{<:Integer}, ::Integer) and div etc.
As discussed on discourse, this seems like the only sensible definition:
mod(z::Complex{<:Integer}, n::Integer) = Complex(mod(real(z), n), mod(imag(z), n))
and similarly for div, rem (and probably divrem).
Should be easy to create a patch (much simpler than #35374): just add the 1-line definitions analogous to the one above, docs, news, and a test.
I disagree, I think it should be
mod(z::Complex, n) = Complex(mod(real(z), n), mod(imag(z), n))
Since this defines mod recursively, you can just let mod of a Real coefficient to handle the dispatch
Yes, that's a reasonable further generalization, although I would be more conservative and restrict it to
mod(z::Complex, n::Real) = ...
If n is not real, then splitting the computation into real and imaginary parts makes less sense (especially since Complex(a,b) expects real arguments).
Edit: sorry I was mistaken in my last post.
I don't quite see the argument for restricting the dispatch though, if it errors, then it errors, why restrict the dispatch?
And why is Complex(1+im,1-im) not allowed? Nested complex numbers are quite reasonable.
The problem with dispatching to something that is unlikely to ever be implemented is that the error message is needlessly confusing, both to users and to future implementors — if we want to support mod(::Complex, ::Complex), we will add that method (ala #35374), opposed to implementing mod(::Real, ::Complex).
As for generalizing the Complex constructor, that's a better subject for a separate issue.
Also, if n is not real, the return value is not necessarily Complex. For example, if n is a quaternion then the return value would not be Complex.
Hello @stevengj @chakravala. I would like to work on this issue. Can you please guide me? I have been using Julia for quite some time but this is the first time I decided to contribute.
@prithvitewatia, welcome!
Basically you just need to add a 1-line definition similar to the one discussed above, e.g. mod(z::Complex, n::Real) = Complex(mod(real(z), n), mod(imag(z), n)), to base/complex.jl, and also add a test and update NEWS.md, and then submit a PR.
See also https://github.com/JuliaLang/julia/blob/master/CONTRIBUTING.md
Is this the obvious definition? I would have guessed that it was this:
mod(z::Complex, n::Real) = Complex(mod(real(z), n), imag(z))
That is, perform one identification on the complex plane, equating z and z+n. Performing two identifications, the second of which rotates n to lie in the imaginary direction, z ~ z + im * n, seems a strange choice to me. What's the logic?
For example, I would expect mod(z, 2pi*im) to leave the real part alone, and identify only in the imaginary direction. This maps you to the fundamental domain of exp. A similar rotation to identify also z ~ z + 2pi in the real direction would be surprising.
@mcabbott I am sorry but I am not able to understand your point. Could you please provide some more examples?
Sure, I can try.
Ordinary trig functions are periodic on the real line, e.g. tand(5) == tand(5 + 180) == tand(5 + 2*180), and this fact is captured by mod(5, 180) == mod(5 + 180, 180): the function x -> mod(x, 180) standardises the argument, making the function one-to-one.
This periodicity still exists in the complex plane: tand(5+7im) ≈ tand(5+7im + 180). The step here is still in the real direction. There is no extra periodicity in the imaginary direction, there is nothing special at all about tand(5+7im + 180im). If mod is still going to capture this behaviour, then mod(x, 180) needs to act only on the real part.
Other functions can have periods in other directions, e.g. f(x) = tand((1+im) * x) obeys f(17) ≈ f(17 + 90*(1-im)). And other functions can be periodic in both real and imaginary directions (e.g. Jacobi trig). But even then, there's little reason to think the period would be the same in the real and imaginary directions (rather than independent parameters).
Now maybe there are other uses of complex mod which I haven't thought of. But the linked discourse thread doesn't mention any.
@mcabbott Thanks for the explanation, now I get your point. But now I am confused about which implementation we should have.
This is because WolframAlpha uses mod(z::complex,n::Real)=Complex(mod(real(z),n),mod(imag(z),n)) whereas languages such as Python and Octave do not support complex types for modulo operator or function.
I didn't think to look, but here's what Mathematica does:
Mod[5+I,10]
Mod[6+I,10] (* real output in -5 to 5, although Mod[6, 10] == 6 *)
Mod[15+I,10]
Mod[16+I,10]
Out[175]= 5+I
Out[176]= -4+I
Out[177]= -5+I
Out[178]= -4+I
Mod[3+8I,10]
Mod[3+18I,10]
Mod[3+25I,10] (* imaginary output in -5 to 5 *)
Out[182]= 3-2 I
Out[183]= 3-2 I
Out[184]= 3+5 I
I think they are getting this from the equivalent of div, help says this:
Mod[m,n] is equivalent to m-n Quotient[m,n].
Mod works with complex numbers, using its definition in terms of Quotient.
Quotient[m,n] is equivalent to Floor[m/n] for integers m and n.
but doesn't seem to say much about non-integer m,n. The behaviour is:
Quotient[5, 10]
Quotient[25, 10]
Out[143]= 0
Out[144]= 2
Quotient[14+2I, 10]
Quotient[14+22I, 10]
Quotient[14+22I, 10I]
Out[154]= 1
Out[155]= 1+2 I
Out[156]= 2-I
Floor[3/2 + 5I/2]
Floor[-3/2 - 5I/2] (* rounds down *)
Out[188]= 1+2 I
Out[189]= -2-3 I
Quotient[14+22I, 1+2I] (* rounds to nearest? *)
(14+22I)/( 1+2I) // N
Floor[11.6` -1.2` I]
Out[173]= 12-I
Out[174]= 11.6 -1.2 I
Out[175]= 11-2 I
Note that in Julia, floor(1+im) and div(1+im,2) are errors.
Here's another argument:
x ≡ y (mod z) means that, for some integer n, we have x = y + n * z. The function is mod is supposed to be such that mod(x,z) == mod(y,z) if and only if x ≡ y (mod z).
That makes sense for integer x,y,z. It also makes sense for real-valued x,y,z, but we do not change the fact that n is an integer. The obvious extension to complex numbers is to allow complex x,y,z, but without changing the fact that n is an integer.
The proposed definition does something different. For complex x,y and real z, it takes n to be a real integer plus a complex integer. That corresponds to something like ∃ x′ such that x ≡ x′ (mod z) and x′ ≡ y (mod im*z). What's the argument for this being natural, or useful? With the simpler definition, this one would be mod(mod(x, n), im*n), explicitly acting in two directions.
@mcabbott, the proposed PR only adds z mod n where n is real, not mod z. I think that is uncontroversial?
@stevengj I still think it should be mod(z::Complex, n::Real) = Complex(mod(real(z), n), imag(z)) for this case. I've tried to argue various ways above. The proposed behaviour seems right for a tuple, mod.((x,y), n) but not for z = x+im*y; the complex plane has more structure.
What's the argument for this behaviour? Is it standard somewhere that I don't know about? Is it copied from some other language?
Can someone explain why when I try to find the order of 10(mod 71) online I get n = 35 but when I try Mod(10^35 , 71) I get 24? When I compute Mod(10^70, 71) I get 0. What's going on here?
@jdvalderra questions should go to discourse. Github is made for development. What you're seeing is that Julia uses Int64, so 10^70==0. You should use mod(big(10)^70,71) instead.
The proposed definition treats complex numbers as a vector space over the reals (or integers), and inherits much structure from it. (In addition, there is complex multiplication.)
Scalar multiplication and scalar division are inherited from being a vector space. div/mod/rem can be inherited in the same way. The special structure imbued by complex multiplication does not play a role here.
Other languages (e.g. Mathematica) make this choice as well. It's also a natural choice for Julia, given that complex numbers are represented as a vector of its real and imaginary components.