From: Simon Tatham Date: Wed, 16 Apr 2025 06:18:01 +0000 (+0100) Subject: Another optimisation for multiplication. X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ian/git?a=commitdiff_plain;h=c01ea53e50a45f73f9e8a5f5c9fb9520b3ee03dc;p=nimber.git Another optimisation for multiplication. Had this idea in the middle of the night, and it works well! --- diff --git a/src/finite.rs b/src/finite.rs index 19a0116..a6f43d1 100644 --- a/src/finite.rs +++ b/src/finite.rs @@ -810,12 +810,17 @@ impl<'a> FiniteNimberRef<'a> { None => self.into(), } } +} + +impl<'a, 'b> Mul> for FiniteNimberRef<'b> { + type Output = FiniteNimber; /// Recursively multiply two nimbers. Underlies the [`Mul`] /// implementations on `FiniteNimber` itself. /// - /// The most obvious formula for multiplying a = ah\*t+al by b = - /// bh\*t+bl is + /// If the two input nimbers have the same level, that's the + /// interesting case. The most obvious formula for multiplying a = + /// ah\*t+al by b = bh\*t+bl is /// /// ```text /// (ah t + al)(bh t + bl) @@ -846,35 +851,53 @@ impl<'a> FiniteNimberRef<'a> { /// ```text /// (k + al bl) t + (ah bh h + al bl) /// ``` - fn mul_recurse( - self, - other: FiniteNimberRef<'a>, - level: usize, - ) -> FiniteNimber { - match level.checked_sub(1) { - Some(sublevel) => { - let (alo, ahi) = self.split(sublevel); - let (blo, bhi) = other.split(sublevel); - let karatsuba = (alo + ahi) * (blo + bhi); - let albl = alo * blo; - let ahbh = ahi * bhi; - (&albl + ahbh.to_ref().mul_by_h(sublevel)) - .to_ref() - .join((karatsuba + &albl).to_ref(), sublevel) - } + /// + /// However, if we're given two nimbers to multiply _not_ at the + /// same level, we can do something much easier: only split up the + /// larger nimber (say b), and simply multiply each of its halves + /// by the smaller one in the obvious way: + /// + /// ```text + /// a(bh t + bl) + /// = (a bh) t + (a bl) + /// ``` + fn mul(self, other: FiniteNimberRef<'a>) -> FiniteNimber { + let slevel = self.level(); + let olevel = other.level(); - // At level 0, we're in GF(2), so multiplication looks - // like bitwise AND. - None => FiniteNimber::from(self.low_word() & other.low_word()), - } - } -} + // Sort the two inputs so that alevel <= blevel + let (a, b, alevel, blevel) = if slevel > olevel { + (other, self, olevel, slevel) + } else { + (self, other, slevel, olevel) + }; -impl<'a, 'b> Mul> for FiniteNimberRef<'b> { - type Output = FiniteNimber; - fn mul(self, other: FiniteNimberRef<'a>) -> FiniteNimber { - let level = max(self.level(), other.level()); - self.mul_recurse(other, level) + if alevel < blevel { + // The easy case, where we only need to split up b and + // recurse twice to the next level down. + let sublevel = blevel - 1; + let (blo, bhi) = b.split(sublevel); + (blo * a).to_ref().join((bhi * a).to_ref(), sublevel) + } else { + // The hard case, where we must split up both nimbers and + // do three full recursive multiplications plus a mul_by_h. + match alevel.checked_sub(1) { + Some(sublevel) => { + let (alo, ahi) = a.split(sublevel); + let (blo, bhi) = b.split(sublevel); + let karatsuba = (alo + ahi) * (blo + bhi); + let albl = alo * blo; + let ahbh = ahi * bhi; + (&albl + ahbh.to_ref().mul_by_h(sublevel)) + .to_ref() + .join((karatsuba + &albl).to_ref(), sublevel) + } + + // At level 0, we're in GF(2), so multiplication looks + // like bitwise AND. + None => FiniteNimber::from(a.low_word() & b.low_word()), + } + } } } @@ -955,7 +978,7 @@ impl<'a, 'b> Div> for FiniteNimberRef<'b> { fn div(self, other: FiniteNimberRef<'a>) -> FiniteNimber { let level = max(self.level(), other.level()); let inverse = other.inverse_recurse(level).expect("Division by zero"); - self.mul_recurse(inverse.to_ref(), level) + self * inverse.to_ref() } }