chiark / gitweb /
Another optimisation for multiplication.
authorSimon Tatham <anakin@pobox.com>
Wed, 16 Apr 2025 06:18:01 +0000 (07:18 +0100)
committerSimon Tatham <anakin@pobox.com>
Wed, 16 Apr 2025 18:30:13 +0000 (19:30 +0100)
Had this idea in the middle of the night, and it works well!

src/finite.rs

index 19a01165518dc2609de7df70abdbdc846f28a69c..a6f43d1b223993781fb0e365ae024b2e5ba1acf8 100644 (file)
@@ -810,12 +810,17 @@ impl<'a> FiniteNimberRef<'a> {
             None => self.into(),
         }
     }
+}
+
+impl<'a, 'b> Mul<FiniteNimberRef<'a>> 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<FiniteNimberRef<'a>> 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<FiniteNimberRef<'a>> 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()
     }
 }