-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add invmod (modular multiplicative inverse) (#2368)
* add invmod (modular multiplicative inverse) * implement (most) suggestions * style error * fix NaN tests Co-authored-by: Jos de Jong <wjosdejong@gmail.com>
- Loading branch information
Showing
5 changed files
with
137 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
export const invmodDocs = { | ||
name: 'invmod', | ||
category: 'Arithmetic', | ||
syntax: [ | ||
'invmod(a, b)' | ||
], | ||
description: 'Calculate the (modular) multiplicative inverse of a modulo b. Solution to the equation ax ≣ 1 (mod b)', | ||
examples: [ | ||
'invmod(8, 12)=NaN', | ||
'invmod(7, 13)=2', | ||
'math.invmod(15151, 15122)=10429' | ||
], | ||
seealso: ['gcd', 'xgcd'] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { factory } from '../../utils/factory.js' | ||
|
||
const name = 'invmod' | ||
const dependencies = ['typed', 'config', 'BigNumber', 'xgcd', 'equal', 'smaller', 'mod', 'add', 'isInteger'] | ||
|
||
export const createInvmod = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, BigNumber, xgcd, equal, smaller, mod, add, isInteger }) => { | ||
/** | ||
* Calculate the (modular) multiplicative inverse of a modulo b. Solution to the equation `ax ≣ 1 (mod b)` | ||
* See https://en.wikipedia.org/wiki/Modular_multiplicative_inverse. | ||
* | ||
* Syntax: | ||
* | ||
* math.invmod(a, b) | ||
* | ||
* Examples: | ||
* | ||
* math.invmod(8, 12) // returns NaN | ||
* math.invmod(7, 13) // return 2 | ||
* math.invmod(15151, 15122) // returns 10429 | ||
* | ||
* See also: | ||
* | ||
* gcd, xgcd | ||
* | ||
* @param {number | BigNumber} a An integer number | ||
* @param {number | BigNumber} b An integer number | ||
* @return {number | BigNumber } Returns an integer number | ||
* where `invmod(a,b)*a ≣ 1 (mod b)` | ||
*/ | ||
return typed(name, { | ||
'number, number': invmod, | ||
'BigNumber, BigNumber': invmod | ||
}) | ||
|
||
function invmod (a, b) { | ||
if (!isInteger(a) || !isInteger(b)) throw new Error('Parameters in function invmod must be integer numbers') | ||
a = mod(a, b) | ||
if (equal(b, 0)) throw new Error('Divisor must be non zero') | ||
let res = xgcd(a, b) | ||
res = res.valueOf() | ||
let [gcd, inv] = res | ||
if (!equal(gcd, BigNumber(1))) return NaN | ||
inv = mod(inv, b) | ||
if (smaller(inv, BigNumber(0))) inv = add(inv, b) | ||
return inv | ||
} | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
// test invmod | ||
import assert from 'assert' | ||
|
||
import math from '../../../../src/defaultInstance.js' | ||
const { invmod, complex, bignumber } = math | ||
|
||
describe('invmod', function () { | ||
it('should find the multiplicative inverse for basic cases', () => { | ||
assert.strictEqual(invmod(2, 7), 4) | ||
assert.strictEqual(invmod(3, 11), 4) | ||
assert.strictEqual(invmod(10, 17), 12) | ||
}) | ||
|
||
it('should return NaN when there is no multiplicative inverse', () => { | ||
assert(isNaN(invmod(3, 15))) | ||
assert(isNaN(invmod(14, 7))) | ||
assert(isNaN(invmod(42, 1200))) | ||
}) | ||
|
||
it('should work when a≥b', () => { | ||
assert.strictEqual(invmod(4, 3), 1) | ||
assert(isNaN(invmod(7, 7))) | ||
}) | ||
|
||
it('should work for negative values', () => { | ||
assert.strictEqual(invmod(-2, 7), 3) | ||
assert.strictEqual(invmod(-2000000, 21), 10) | ||
}) | ||
|
||
it('should calculate invmod for BigNumbers', () => { | ||
assert.deepStrictEqual(invmod(bignumber(13), bignumber(25)), bignumber(2)) | ||
assert.deepStrictEqual(invmod(bignumber(-7), bignumber(48)), bignumber(41)) | ||
}) | ||
|
||
it('should calculate invmod for mixed BigNumbers and Numbers', () => { | ||
assert.deepStrictEqual(invmod(bignumber(44), 7), bignumber(4)) | ||
assert.deepStrictEqual(invmod(4, math.bignumber(15)), bignumber(4)) | ||
}) | ||
|
||
it('should throw an error if b is zero', function () { | ||
assert.throws(function () { invmod(1, 0) }, /Divisor must be non zero/) | ||
}) | ||
|
||
it('should throw an error if only one argument', function () { | ||
assert.throws(function () { invmod(1) }, /TypeError: Too few arguments/) | ||
}) | ||
|
||
it('should throw an error for non-integer numbers', function () { | ||
assert.throws(function () { invmod(2, 4.1) }, /Parameters in function invmod must be integer numbers/) | ||
assert.throws(function () { invmod(2.3, 4) }, /Parameters in function invmod must be integer numbers/) | ||
}) | ||
|
||
it('should throw an error with complex numbers', function () { | ||
assert.throws(function () { invmod(complex(1, 3), 2) }, /TypeError: Unexpected type of argument/) | ||
}) | ||
|
||
it('should convert strings to numbers', function () { | ||
assert.strictEqual(invmod('7', '15'), 13) | ||
assert.strictEqual(invmod(7, '15'), 13) | ||
assert.strictEqual(invmod('7', 15), 13) | ||
|
||
assert.throws(function () { invmod('a', 8) }, /Cannot convert "a" to a number/) | ||
}) | ||
|
||
it('should throw an error with units', function () { | ||
assert.throws(function () { invmod(math.unit('5cm'), 2) }, /TypeError: Unexpected type of argument/) | ||
}) | ||
|
||
it('should LaTeX invmod', function () { | ||
const expression = math.parse('invmod(2,3)') | ||
assert.strictEqual(expression.toTex(), '\\mathrm{invmod}\\left(2,3\\right)') | ||
}) | ||
}) |