From 99262f5d702a0691f5aac71e46616755406c4c60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Bystr=C3=B6m=20Ericsson?= Date: Tue, 31 Oct 2023 14:22:13 +0100 Subject: [PATCH] [NBKCoreKit] Modular multiplicative inverse. --- ...rBinaryInteger+GreatestCommonDivisor.swift | 8 +- ...Integer+ModularMultiplicativeInverse.swift | 68 ++++++++ ...Integer+ModularMultiplicativeInverse.swift | 151 ++++++++++++++++++ 3 files changed, 223 insertions(+), 4 deletions(-) create mode 100644 Sources/NBKCoreKit/Private/NBKProperBinaryInteger+ModularMultiplicativeInverse.swift create mode 100644 Tests/NBKCoreKitTests/Private/NBKProperBinaryInteger+ModularMultiplicativeInverse.swift diff --git a/Sources/NBKCoreKit/Private/NBKProperBinaryInteger+GreatestCommonDivisor.swift b/Sources/NBKCoreKit/Private/NBKProperBinaryInteger+GreatestCommonDivisor.swift index 40149fd3..606b7838 100644 --- a/Sources/NBKCoreKit/Private/NBKProperBinaryInteger+GreatestCommonDivisor.swift +++ b/Sources/NBKCoreKit/Private/NBKProperBinaryInteger+GreatestCommonDivisor.swift @@ -193,7 +193,7 @@ extension NBK.ProperBinaryInteger { /// ### Quotients of dividing by GCD /// /// ```swift - /// if !result.isZero, result <= Integer.max { + /// if !result.isZero, let result = Integer(exactly: result) { /// precondition(lhsQuotient == lhs / result) /// precondition(rhsQuotient == rhs / result) /// } @@ -314,7 +314,7 @@ extension NBK.ProperBinaryInteger.GreatestCommonDivisorByEuclideanAlgorithm wher self.x = (001, 000) reduce: while !self.r.1.isZero { - let division = self.r.0.quotientAndRemainder(dividingBy: self.r.1) + let division = self.r.0.quotientAndRemainder(dividingBy: r.1) self.i += (00000001) as Integer.Digit self.r = (self.r.1, division.remainder) self.x = (self.x.1, division.quotient * self.x.1 + self.x.0) @@ -355,7 +355,7 @@ extension NBK.ProperBinaryInteger.GreatestCommonDivisorByEuclideanAlgorithm wher self.y = (000, 001) reduce: while !self.r.1.isZero { - let division = self.r.0.quotientAndRemainder(dividingBy: self.r.1) + let division = self.r.0.quotientAndRemainder(dividingBy: r.1) self.i += (00000001) as Integer.Digit self.r = (self.r.1, division.remainder) self.y = (self.y.1, division.quotient * self.y.1 + self.y.0) @@ -399,7 +399,7 @@ extension NBK.ProperBinaryInteger.GreatestCommonDivisorByEuclideanAlgorithm wher self.y = (000, 001) reduce: while !self.r.1.isZero { - let division = self.r.0.quotientAndRemainder(dividingBy: self.r.1) + let division = self.r.0.quotientAndRemainder(dividingBy: r.1) self.i += (00000001) as Integer.Digit self.r = (self.r.1, division.remainder) self.x = (self.x.1, division.quotient * self.x.1 + self.x.0) diff --git a/Sources/NBKCoreKit/Private/NBKProperBinaryInteger+ModularMultiplicativeInverse.swift b/Sources/NBKCoreKit/Private/NBKProperBinaryInteger+ModularMultiplicativeInverse.swift new file mode 100644 index 00000000..93022834 --- /dev/null +++ b/Sources/NBKCoreKit/Private/NBKProperBinaryInteger+ModularMultiplicativeInverse.swift @@ -0,0 +1,68 @@ +//=----------------------------------------------------------------------------= +// This source file is part of the Numberick open source project. +// +// Copyright (c) 2023 Oscar Byström Ericsson +// Licensed under Apache License, Version 2.0 +// +// See http://www.apache.org/licenses/LICENSE-2.0 for license information. +//=----------------------------------------------------------------------------= + +//*============================================================================* +// MARK: * NBK x Proper Binary Integer x Modular Mul. Inverse +//*============================================================================* + +extension NBK.ProperBinaryInteger { + + //=------------------------------------------------------------------------= + // MARK: Utilities + //=------------------------------------------------------------------------= + + /// Returns the modular multiplicative inverse of `integer` modulo `modulus`, if it exists. + /// + /// - Returns: A value from `0` to `modulus` or `nil`. + /// + @inlinable public static func modularMultiplicativeInverse(of integer: Integer, mod modulus: Integer) -> Integer? { + //=--------------------------------------= + let lhsIsLessThanZero = integer.isLessThanZero + let rhsIsLessThanZero = modulus.isLessThanZero + //=--------------------------------------= + if rhsIsLessThanZero { return nil } + //=--------------------------------------= + guard let unsigned = Magnitude.modularMultiplicativeInverse(of: integer.magnitude, mod: modulus.magnitude) else { return nil } + //=--------------------------------------= + var inverse = Integer(sign: NBK.sign(lhsIsLessThanZero), magnitude: unsigned)! + if inverse.isLessThanZero { inverse += modulus } + return inverse as Integer as Integer as Integer + } +} + +//*============================================================================* +// MARK: * NBK x Proper Binary Integer x Modular Mul. Inverse x Unsigned +//*============================================================================* + +extension NBK.ProperBinaryInteger where Integer: NBKUnsignedInteger { + + //=------------------------------------------------------------------------= + // MARK: Utilities + //=------------------------------------------------------------------------= + + /// Returns the modular multiplicative inverse of `integer` modulo `modulus`, if it exists. + /// + /// - Returns: A value from `0` to `modulus` or `nil`. + /// + @inlinable public static func modularMultiplicativeInverse(of integer: Integer, mod modulus: Integer) -> Integer? { + //=--------------------------------------= + switch modulus.compared(to: 1 as Integer.Digit) { + case 1: break; + case 0: return Integer.zero + default: return nil } + //=--------------------------------------= + let extended = self.greatestCommonDivisorByEuclideanAlgorithm10(of: integer, and: modulus) + //=--------------------------------------= + guard extended.result.compared(to: 1 as Integer.Digit).isZero else { + return nil // the arguments must be coprime + } + //=--------------------------------------= + return extended.iteration.isOdd ? modulus - extended.lhsCoefficient : extended.lhsCoefficient + } +} diff --git a/Tests/NBKCoreKitTests/Private/NBKProperBinaryInteger+ModularMultiplicativeInverse.swift b/Tests/NBKCoreKitTests/Private/NBKProperBinaryInteger+ModularMultiplicativeInverse.swift new file mode 100644 index 00000000..a0ae9706 --- /dev/null +++ b/Tests/NBKCoreKitTests/Private/NBKProperBinaryInteger+ModularMultiplicativeInverse.swift @@ -0,0 +1,151 @@ +//=----------------------------------------------------------------------------= +// This source file is part of the Numberick open source project. +// +// Copyright (c) 2023 Oscar Byström Ericsson +// Licensed under Apache License, Version 2.0 +// +// See http://www.apache.org/licenses/LICENSE-2.0 for license information. +//=----------------------------------------------------------------------------= + +#if DEBUG + +import NBKCoreKit +import XCTest + +//*============================================================================* +// MARK: * NBK x Proper Binary Integer x Modular Mul. Inverse +//*============================================================================* + +final class NBKProperBinaryIntegerTestsOnModularMultiplicativeInverse: XCTestCase { + + typealias T = NBK.PBI + + //=------------------------------------------------------------------------= + // MARK: Tests + //=------------------------------------------------------------------------= + + func testSomeSmallPrimes() { + NBKAssertModularMultiplicativeInverse( 2 as Int, 31 as Int, 16 as Int) // primes: 1, 11 + NBKAssertModularMultiplicativeInverse( 3 as Int, 79 as Int, 53 as Int) // primes: 2, 22 + NBKAssertModularMultiplicativeInverse( 5 as Int, 137 as Int, 55 as Int) // primes: 3, 33 + NBKAssertModularMultiplicativeInverse( 7 as Int, 193 as Int, 138 as Int) // primes: 4, 44 + NBKAssertModularMultiplicativeInverse( 11 as Int, 257 as Int, 187 as Int) // primes: 5, 55 + NBKAssertModularMultiplicativeInverse( 13 as Int, 317 as Int, 122 as Int) // primes: 6, 66 + NBKAssertModularMultiplicativeInverse( 17 as Int, 389 as Int, 206 as Int) // primes: 7, 77 + NBKAssertModularMultiplicativeInverse( 19 as Int, 457 as Int, 433 as Int) // primes: 8, 88 + NBKAssertModularMultiplicativeInverse( 23 as Int, 523 as Int, 91 as Int) // primes: 9, 99 + + NBKAssertModularMultiplicativeInverse( 2 as Int, 607 as Int, 304 as Int) // primes: 1, 111 + NBKAssertModularMultiplicativeInverse( 3 as Int, 1399 as Int, 933 as Int) // primes: 2, 222 + NBKAssertModularMultiplicativeInverse( 5 as Int, 2239 as Int, 448 as Int) // primes: 3, 333 + NBKAssertModularMultiplicativeInverse( 7 as Int, 3119 as Int, 2228 as Int) // primes: 4, 444 + NBKAssertModularMultiplicativeInverse( 11 as Int, 4019 as Int, 2923 as Int) // primes: 5, 555 + NBKAssertModularMultiplicativeInverse( 13 as Int, 4973 as Int, 4208 as Int) // primes: 6, 666 + NBKAssertModularMultiplicativeInverse( 17 as Int, 5903 as Int, 1389 as Int) // primes: 7, 777 + NBKAssertModularMultiplicativeInverse( 19 as Int, 6907 as Int, 6180 as Int) // primes: 8, 888 + NBKAssertModularMultiplicativeInverse( 23 as Int, 7907 as Int, 4813 as Int) // primes: 9, 999 + + NBKAssertModularMultiplicativeInverse( 31 as Int, 607 as Int, 235 as Int) // primes: 11, 111 + NBKAssertModularMultiplicativeInverse( 79 as Int, 1399 as Int, 974 as Int) // primes: 22, 222 + NBKAssertModularMultiplicativeInverse(137 as Int, 2239 as Int, 1667 as Int) // primes: 33, 333 + NBKAssertModularMultiplicativeInverse(193 as Int, 3119 as Int, 905 as Int) // primes: 44, 444 + NBKAssertModularMultiplicativeInverse(257 as Int, 4019 as Int, 2377 as Int) // primes: 55, 555 + NBKAssertModularMultiplicativeInverse(317 as Int, 4973 as Int, 4722 as Int) // primes: 66, 666 + NBKAssertModularMultiplicativeInverse(389 as Int, 5903 as Int, 2170 as Int) // primes: 77, 777 + NBKAssertModularMultiplicativeInverse(457 as Int, 6907 as Int, 4383 as Int) // primes: 88, 888 + NBKAssertModularMultiplicativeInverse(523 as Int, 7907 as Int, 2933 as Int) // primes: 99, 999 + } + + //=------------------------------------------------------------------------= + // MARK: Tests x Simple Cases + //=------------------------------------------------------------------------= + + func testInverseModuloZeroIsNil() { + for x in 0 as Int ..< 10 { + NBKAssertModularMultiplicativeInverse(x as Int, 0 as Int, nil as Int?) + } + } + + func testInverseModuloOneIsZero() { + for x in 0 as Int ..< 10 { + NBKAssertModularMultiplicativeInverse(x as Int, 1 as Int, 000 as Int?) + } + } + + func testZeroHasNoModularInverseForEachModulusGreaterThanOne() { + for x in 2 as Int ..< 10 { + NBKAssertModularMultiplicativeInverse(0 as Int, x as Int, nil as Int?) + } + } + + func testOneIsAlwaysItsOwnInverseForEachModulusGreaterThanOne() { + for x in 2 as Int ..< 10 { + NBKAssertModularMultiplicativeInverse(1 as Int, x as Int, 001 as Int?) + } + } + + func testThereIsNoInverseForAnyModulusLessThanOne() { + for x in -10 as Int ..< 1 { + NBKAssertModularMultiplicativeInverse(1 as Int, x as Int, nil as Int?) + } + } + + func testInverseIsNilWhenInputsAreNotCoprime() { + NBKAssertModularMultiplicativeInverse(1 * 1 * 1 as Int, 2 * 3 * 5 as Int, 001 as Int?) + NBKAssertModularMultiplicativeInverse(2 * 1 * 1 as Int, 2 * 3 * 5 as Int, nil as Int?) + NBKAssertModularMultiplicativeInverse(1 * 3 * 1 as Int, 2 * 3 * 5 as Int, nil as Int?) + NBKAssertModularMultiplicativeInverse(1 * 1 * 5 as Int, 2 * 3 * 5 as Int, nil as Int?) + NBKAssertModularMultiplicativeInverse(2 * 3 * 1 as Int, 2 * 3 * 5 as Int, nil as Int?) + NBKAssertModularMultiplicativeInverse(2 * 1 * 5 as Int, 2 * 3 * 5 as Int, nil as Int?) + NBKAssertModularMultiplicativeInverse(1 * 3 * 5 as Int, 2 * 3 * 5 as Int, nil as Int?) + NBKAssertModularMultiplicativeInverse(2 * 3 * 5 as Int, 2 * 3 * 5 as Int, nil as Int?) + NBKAssertModularMultiplicativeInverse(7 * 7 * 7 as Int, 2 * 3 * 5 as Int, 007 as Int?) + } +} + +//*============================================================================* +// MARK: * NBK x Proper Binary Integer x Modular Mul. Inverse x Assertions +//*============================================================================* + +private func NBKAssertModularMultiplicativeInverse( +_ lhs: T, _ rhs: T, _ expectation: T?, +file: StaticString = #file, line: UInt = #line) { + //=------------------------------------------= + if !lhs.addingReportingOverflow(rhs).overflow { + NBKAssertModularMultiplicativeInverseInvariants(lhs + rhs, rhs, expectation, file: file, line: line) + } + + brr: do { + NBKAssertModularMultiplicativeInverseInvariants(lhs + 000, rhs, expectation, file: file, line: line) + } + + if !lhs.subtractingReportingOverflow(rhs).overflow { + NBKAssertModularMultiplicativeInverseInvariants(lhs - rhs, rhs, expectation, file: file, line: line) + } +} + +private func NBKAssertModularMultiplicativeInverseInvariants( +_ lhs: T, _ rhs: T, _ expectation: T?, +file: StaticString = #file, line: UInt = #line) { + //=--------------------------------------= + let inverse = NBK.PBI.modularMultiplicativeInverse(of: lhs, mod: rhs) + //=--------------------------------------= + XCTAssertEqual(inverse, expectation, file: file, line: line) + //=--------------------------------------= + if let inverse { + var remainder = rhs.dividingFullWidth(lhs.multipliedFullWidth(by: inverse)).remainder + if remainder.isLessThanZero { + remainder += rhs + } + + XCTAssert(0000 ..< rhs ~= inverse, file: file, line: line) + XCTAssertEqual(remainder, 1 % rhs, file: file, line: line) + XCTAssertEqual(inverse.isZero, rhs == 1, file: file, line: line) + } + //=--------------------------------------= + if T.isSigned, !lhs.isLessThanZero, !rhs.isLessThanZero { + NBKAssertModularMultiplicativeInverseInvariants(lhs.magnitude, rhs.magnitude, expectation?.magnitude, file: file, line: line) + } +} + +#endif