mirror of
https://github.com/josdejong/mathjs.git
synced 2026-01-25 15:07:57 +00:00
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>
This commit is contained in:
parent
af0758a036
commit
7beac55201
@ -136,6 +136,7 @@ import { bitOrDocs } from './function/bitwise/bitOr.js'
|
||||
import { bitNotDocs } from './function/bitwise/bitNot.js'
|
||||
import { bitAndDocs } from './function/bitwise/bitAnd.js'
|
||||
import { xgcdDocs } from './function/arithmetic/xgcd.js'
|
||||
import { invmodDocs } from './function/arithmetic/invmod.js'
|
||||
import { unaryPlusDocs } from './function/arithmetic/unaryPlus.js'
|
||||
import { unaryMinusDocs } from './function/arithmetic/unaryMinus.js'
|
||||
import { squareDocs } from './function/arithmetic/square.js'
|
||||
@ -369,6 +370,7 @@ export const embeddedDocs = {
|
||||
unaryMinus: unaryMinusDocs,
|
||||
unaryPlus: unaryPlusDocs,
|
||||
xgcd: xgcdDocs,
|
||||
invmod: invmodDocs,
|
||||
|
||||
// functions - bitwise
|
||||
bitAnd: bitAndDocs,
|
||||
|
||||
14
src/expression/embeddedDocs/function/arithmetic/invmod.js
Normal file
14
src/expression/embeddedDocs/function/arithmetic/invmod.js
Normal file
@ -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']
|
||||
}
|
||||
@ -53,6 +53,7 @@ export { createSqrt } from './function/arithmetic/sqrt.js'
|
||||
export { createSquare } from './function/arithmetic/square.js'
|
||||
export { createSubtract } from './function/arithmetic/subtract.js'
|
||||
export { createXgcd } from './function/arithmetic/xgcd.js'
|
||||
export { createInvmod } from './function/arithmetic/invmod.js'
|
||||
export { createDotMultiply } from './function/arithmetic/dotMultiply.js'
|
||||
export { createBitAnd } from './function/bitwise/bitAnd.js'
|
||||
export { createBitNot } from './function/bitwise/bitNot.js'
|
||||
|
||||
47
src/function/arithmetic/invmod.js
Normal file
47
src/function/arithmetic/invmod.js
Normal file
@ -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
|
||||
}
|
||||
})
|
||||
73
test/unit-tests/function/arithmetic/invmod.test.js
Normal file
73
test/unit-tests/function/arithmetic/invmod.test.js
Normal file
@ -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)')
|
||||
})
|
||||
})
|
||||
Loading…
x
Reference in New Issue
Block a user