Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix gcd & lcm sign and optimize gcdx and invmod #4811

Merged
merged 5 commits into from
Nov 14, 2013

Conversation

stevengj
Copy link
Member

The attached patch fixes several bugs (including #4810):

  • gcd, lcm, and gcdx, returned inconsistent signs between BigInt and other integer types. Now they always return a positive gcd and lcm.
  • invmod was broken for negative arguments, since it was incorrectly interpreting a gcd of -1 as meaning that the inverse did not exist.
  • Rational construction was broken for BigInt because of the gcd sign inconsistency.
  • gcdx was not type-stable (it promoted a result to Int in one case).

I also rewrote gcdx non-recursively to make it about 20x faster on my machine, and use the GMP invmod function directly for BigInt.

@StefanKarpinski
Copy link
Member

So my take on this is that we should cherry-pick the rational constructor fix for 0.2 and wait on the rest.

@ivarne
Copy link
Member

ivarne commented Nov 14, 2013

You should also document the behaviour of gcd to always return a positive number.

@JeffBezanson
Copy link
Member

I'd prefer to merge this whole PR. In any case, gcd was not documented in enough detail before.

@stevengj
Copy link
Member Author

Another omission is that we don't seem to have any test coverage for gcdx etc. Should there be a new test/intfuncs.jl file or would this go into one of the existing test files?

@StefanKarpinski
Copy link
Member

I guess we could merge this, although I worry that someone else may already be depending on the old sign behavior of gcd and lcm.

@stevengj
Copy link
Member Author

I'm adding a test case to test/numbers.jl, and found another bug: lcm(0,0) throws an exception, as opposed to returning the correct result (0). Patch forthcoming.

@stevengj
Copy link
Member Author

@StefanKarpinski, since the old sign behavior was undocumented, it's not unreasonable to treat it as a bug and to say that people who relied on it are on their own. (In practice, most people doing gcd-like things are probably working with nonnegative integers, though.)

@stevengj
Copy link
Member Author

Hmm, another inconsistency: powermod(3,7,-5) gives -3, but with BigInt it gives 2.

Looks like this can be fixed simply by using rem instead of mod in the powermod implementation.

@stevengj
Copy link
Member Author

More problems with powermod: powermod(1,0,1) gives 1, not zero, and powermod(1,0,0) also gives 1 rather than throwing an exception. The lack of systematic test coverage really shows.

@JeffBezanson
Copy link
Member

The issue is that GMP defines mod to be non-negative, while our mod has the same sign as the divisor.

@stevengj
Copy link
Member Author

@JeffBezanson, yes I noticed. The right thing is to just change the sign in the BigInt implementation, to enforce powermod(a,b,m) == mod(a^b, m).

Another bug: the powermod implementation is not currently type-stable: e.g. returning one(b) for an exponent of 0 is wrong, since it should be one(promote_type(typeof(b),typeof(m))).

@JeffBezanson
Copy link
Member

I'm going to merge this and then fix these powermod problems.

JeffBezanson added a commit that referenced this pull request Nov 14, 2013
fix gcd & lcm sign and optimize gcdx and invmod
@JeffBezanson JeffBezanson merged commit dda7460 into JuliaLang:master Nov 14, 2013
@stevengj
Copy link
Member Author

I have a patch to fix the powermod problems, I'll push it.

@staticfloat
Copy link
Member

Merging this has broken Travis. Here's the test failure log, looks like a test of gcdx hasn't been updated/was broken:

test failed: :((gcdx(i,j)==gcdx(ib,jb)))
 in error at error.jl:21
 in default_handler at test.jl:19
 in do_test at test.jl:39
 in anonymous at no file:1524
 in runtests at /tmp/julia/share/julia/test/testdefs.jl:5
 in anonymous at multi.jl:834
 in run_work_thunk at multi.jl:575
 in anonymous at task.jl:834
at numbers.jl:1526

@JeffBezanson
Copy link
Member

This must be due to a GMP version difference. For gcdx(-20,-20), my GMP gives (20,0,-1) but the one on Travis gives (20,-1,0). Maybe we should extend the b==0 special case to also get called for a==b?

@JeffBezanson
Copy link
Member

A further problem is that since our mod gives negative results when the second argument is negative, no inverses exist mod negative numbers.

@stevengj
Copy link
Member Author

Inverses still exist. e.g. mod(7 * 16, -37) gives -36, and -36 is indeed a multiplicative identity element modulo -37.

@JeffBezanson
Copy link
Member

In that case we are just up against GMP's incompatible definition of mod.

@staticfloat
Copy link
Member

Looks like this was fixed by c92e98d. Thanks Jeff!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants