Ruby 4.1.0dev (2026-11-05 revision 15d19e341cedf69b35a840d21328374927e50ec9)
bignum.c
2#include "prism/internal/bignum.h"
3
4#include <assert.h>
5#include <stddef.h>
6#include <stdint.h>
7#include <string.h>
8
13#define PM_BIGNUM_KARATSUBA_THRESHOLD 24
14
21static PRISM_FORCE_INLINE uint32_t
22pm_bignum_base_words_add(uint64_t base, uint32_t *left, uint32_t left_length, const uint32_t *right, uint32_t right_length) {
23 uint32_t carry = 0;
24 uint32_t index = 0;
25
26 for (; index < right_length; index++) {
27 uint64_t sum = (uint64_t) left[index] + right[index] + carry;
28 carry = sum >= base;
29 left[index] = (uint32_t) (carry ? sum - base : sum);
30 }
31
32 for (; carry != 0 && index < left_length; index++) {
33 uint64_t sum = (uint64_t) left[index] + 1;
34 carry = sum == base;
35 left[index] = (uint32_t) (carry ? 0 : sum);
36 }
37
38 return carry;
39}
40
45static PRISM_FORCE_INLINE void
46pm_bignum_base_words_subtract(uint64_t base, uint32_t *left, uint32_t left_length, const uint32_t *right, uint32_t right_length) {
47 (void) left_length;
48 uint32_t borrow = 0;
49 uint32_t index = 0;
50
51 for (; index < right_length; index++) {
52 uint64_t subtrahend = (uint64_t) right[index] + borrow;
53 borrow = left[index] < subtrahend;
54 left[index] = (uint32_t) (borrow ? left[index] + base - subtrahend : left[index] - subtrahend);
55 }
56
57 for (; borrow != 0; index++) {
58 assert(index < left_length);
59 borrow = left[index] == 0;
60 left[index] = (uint32_t) (borrow ? base - 1 : left[index] - 1);
61 }
62}
63
68static uint32_t
69pm_bignum_words_add(uint32_t *left, uint32_t left_length, const uint32_t *right, uint32_t right_length) {
70 return pm_bignum_base_words_add(PM_BIGNUM_BINARY_BASE, left, left_length, right, right_length);
71}
72
77static uint32_t
78pm_bignum_decimal_words_add(uint32_t *left, uint32_t left_length, const uint32_t *right, uint32_t right_length) {
79 return pm_bignum_base_words_add(PM_BIGNUM_DECIMAL_BASE, left, left_length, right, right_length);
80}
81
86static void
87pm_bignum_words_subtract(uint32_t *left, uint32_t left_length, const uint32_t *right, uint32_t right_length) {
88 pm_bignum_base_words_subtract(PM_BIGNUM_BINARY_BASE, left, left_length, right, right_length);
89}
90
95static void
96pm_bignum_decimal_words_subtract(uint32_t *left, uint32_t left_length, const uint32_t *right, uint32_t right_length) {
97 pm_bignum_base_words_subtract(PM_BIGNUM_DECIMAL_BASE, left, left_length, right, right_length);
98}
99
106static void
107pm_bignum_words_multiply_schoolbook(uint32_t *result, const uint32_t *left, uint32_t left_length, const uint32_t *right, uint32_t right_length) {
108 memset(result, 0, sizeof(uint32_t) * (left_length + right_length));
109
110 for (uint32_t right_index = 0; right_index < right_length; right_index++) {
111 uint64_t carry = 0;
112
113 for (uint32_t left_index = 0; left_index < left_length; left_index++) {
114 carry += (uint64_t) left[left_index] * right[right_index] + result[left_index + right_index];
115 result[left_index + right_index] = (uint32_t) carry;
116 carry >>= 32;
117 }
118
119 result[right_index + left_length] = (uint32_t) carry;
120 }
121}
122
129#define PM_BIGNUM_DECIMAL_PRODUCTS 18
130
140static void
141pm_bignum_decimal_words_multiply_schoolbook(uint32_t *result, const uint32_t *left, uint32_t left_length, const uint32_t *right, uint32_t right_length) {
142 if (left_length == 0 || right_length == 0) {
143 memset(result, 0, sizeof(uint32_t) * (left_length + right_length));
144 return;
145 }
146
147 /* The sum of every column is below its number of products times 10**18,
148 * and there are fewer than 2**29 products in a column, so the carry into
149 * the next column is below 2**29 * 10**9 and fits in a uint64_t. */
150 uint64_t carry = 0;
151
152 for (uint32_t index = 0; index < left_length + right_length - 1; index++) {
153 uint32_t left_start = index < right_length ? 0 : index - right_length + 1;
154 uint32_t left_end = index < left_length ? index + 1 : left_length;
155
156 uint64_t low = 0;
157 uint64_t high = 0;
158 uint32_t products = 0;
159
160 for (uint32_t left_index = left_start; left_index < left_end; left_index++) {
161 low += (uint64_t) left[left_index] * right[index - left_index];
162
163 if (++products == PM_BIGNUM_DECIMAL_PRODUCTS) {
164 high += low / PM_BIGNUM_DECIMAL_BASE;
165 low %= PM_BIGNUM_DECIMAL_BASE;
166 products = 0;
167 }
168 }
169
170 low += carry % PM_BIGNUM_DECIMAL_BASE;
171 high += carry / PM_BIGNUM_DECIMAL_BASE;
172
173 result[index] = (uint32_t) (low % PM_BIGNUM_DECIMAL_BASE);
174 carry = high + low / PM_BIGNUM_DECIMAL_BASE;
175 }
176
177 /* The product is below 10**(9 * (left_length + right_length)), so what is
178 * left over is the top word. */
179 assert(carry < PM_BIGNUM_DECIMAL_BASE);
180 result[left_length + right_length - 1] = (uint32_t) carry;
181}
182
183static void pm_bignum_words_multiply(uint32_t *result, const uint32_t *left, uint32_t left_length, const uint32_t *right, uint32_t right_length, pm_bignum_scratch_t *scratch);
184static void pm_bignum_decimal_words_multiply(uint32_t *result, const uint32_t *left, uint32_t left_length, const uint32_t *right, uint32_t right_length, pm_bignum_scratch_t *scratch);
185
196static PRISM_FORCE_INLINE uint32_t
197pm_bignum_radix_add(uint64_t base, uint32_t *left, uint32_t left_length, const uint32_t *right, uint32_t right_length) {
198 return base == PM_BIGNUM_DECIMAL_BASE ? pm_bignum_decimal_words_add(left, left_length, right, right_length) : pm_bignum_words_add(left, left_length, right, right_length);
199}
200
204static PRISM_FORCE_INLINE void
205pm_bignum_radix_subtract(uint64_t base, uint32_t *left, uint32_t left_length, const uint32_t *right, uint32_t right_length) {
206 if (base == PM_BIGNUM_DECIMAL_BASE) {
207 pm_bignum_decimal_words_subtract(left, left_length, right, right_length);
208 } else {
209 pm_bignum_words_subtract(left, left_length, right, right_length);
210 }
211}
212
216static PRISM_FORCE_INLINE void
217pm_bignum_radix_multiply_schoolbook(uint64_t base, uint32_t *result, const uint32_t *left, uint32_t left_length, const uint32_t *right, uint32_t right_length) {
218 if (base == PM_BIGNUM_DECIMAL_BASE) {
219 pm_bignum_decimal_words_multiply_schoolbook(result, left, left_length, right, right_length);
220 } else {
221 pm_bignum_words_multiply_schoolbook(result, left, left_length, right, right_length);
222 }
223}
224
228static PRISM_FORCE_INLINE void
229pm_bignum_radix_multiply(uint64_t base, uint32_t *result, const uint32_t *left, uint32_t left_length, const uint32_t *right, uint32_t right_length, pm_bignum_scratch_t *scratch) {
230 if (base == PM_BIGNUM_DECIMAL_BASE) {
231 pm_bignum_decimal_words_multiply(result, left, left_length, right, right_length, scratch);
232 } else {
233 pm_bignum_words_multiply(result, left, left_length, right, right_length, scratch);
234 }
235}
236
248static PRISM_FORCE_INLINE void
249pm_bignum_radix_karatsuba(uint64_t base, uint32_t *result, const uint32_t *left, uint32_t left_length, const uint32_t *right, uint32_t right_length, pm_bignum_scratch_t *scratch) {
250 if (left_length < right_length) {
251 const uint32_t *words = left;
252 left = right;
253 right = words;
254
255 uint32_t length = left_length;
256 left_length = right_length;
257 right_length = length;
258 }
259
260 if (right_length < PM_BIGNUM_KARATSUBA_THRESHOLD) {
261 pm_bignum_radix_multiply_schoolbook(base, result, left, left_length, right, right_length);
262 return;
263 }
264
265 uint32_t *saved = scratch->cursor;
266
267 /* When left is at least twice as long, splitting both at the same word
268 * would leave the halves badly mismatched, so instead multiply right by
269 * each right-sized piece of left and add the products into the result. */
270 if (left_length >= 2 * right_length) {
271 memset(result, 0, sizeof(uint32_t) * (left_length + right_length));
272 uint32_t *product = pm_bignum_scratch_take(scratch, 2 * right_length);
273
274 for (uint32_t offset = 0; offset < left_length; offset += right_length) {
275 uint32_t piece = left_length - offset < right_length ? left_length - offset : right_length;
276 pm_bignum_radix_multiply(base, product, left + offset, piece, right, right_length, scratch);
277 pm_bignum_radix_add(base, result + offset, left_length + right_length - offset, product, piece + right_length);
278 }
279
280 scratch->cursor = saved;
281 return;
282 }
283
284 uint32_t half = right_length / 2;
285 uint32_t left_high_length = left_length - half;
286 uint32_t right_high_length = right_length - half;
287
288 pm_bignum_radix_multiply(base, result, left, half, right, half, scratch);
289 pm_bignum_radix_multiply(base, result + 2 * half, left + half, left_high_length, right + half, right_high_length, scratch);
290
291 /* Each high half is at least as long as the low half, so adding the low
292 * half into a copy of the high half needs at most one more word. */
293 uint32_t *left_sum = pm_bignum_scratch_take(scratch, left_high_length + 1);
294 memcpy(left_sum, left + half, sizeof(uint32_t) * left_high_length);
295 left_sum[left_high_length] = 0;
296 pm_bignum_radix_add(base, left_sum, left_high_length + 1, left, half);
297
298 uint32_t *right_sum = pm_bignum_scratch_take(scratch, right_high_length + 1);
299 memcpy(right_sum, right + half, sizeof(uint32_t) * right_high_length);
300 right_sum[right_high_length] = 0;
301 pm_bignum_radix_add(base, right_sum, right_high_length + 1, right, half);
302
303 uint32_t middle_length = left_high_length + right_high_length + 2;
304 uint32_t *middle = pm_bignum_scratch_take(scratch, middle_length);
305 pm_bignum_radix_multiply(base, middle, left_sum, left_high_length + 1, right_sum, right_high_length + 1, scratch);
306 pm_bignum_radix_subtract(base, middle, middle_length, result, 2 * half);
307 pm_bignum_radix_subtract(base, middle, middle_length, result + 2 * half, left_high_length + right_high_length);
308
309 while (middle_length > 0 && middle[middle_length - 1] == 0) middle_length--;
310 pm_bignum_radix_add(base, result + half, left_length + right_length - half, middle, middle_length);
311
312 scratch->cursor = saved;
313}
314
319static void
320pm_bignum_words_multiply(uint32_t *result, const uint32_t *left, uint32_t left_length, const uint32_t *right, uint32_t right_length, pm_bignum_scratch_t *scratch) {
321 pm_bignum_radix_karatsuba(PM_BIGNUM_BINARY_BASE, result, left, left_length, right, right_length, scratch);
322}
323
328static void
329pm_bignum_decimal_words_multiply(uint32_t *result, const uint32_t *left, uint32_t left_length, const uint32_t *right, uint32_t right_length, pm_bignum_scratch_t *scratch) {
330 pm_bignum_radix_karatsuba(PM_BIGNUM_DECIMAL_BASE, result, left, left_length, right, right_length, scratch);
331}
332
338static PRISM_FORCE_INLINE void
339pm_bignum_radix_add_bignum(uint64_t base, pm_bignum_t *left, const pm_bignum_t *right) {
340 if (left->length < right->length) {
341 memset(left->words + left->length, 0, sizeof(uint32_t) * (right->length - left->length));
342 left->length = right->length;
343 }
344
345 uint32_t carry = pm_bignum_radix_add(base, left->words, left->length, right->words, right->length);
346 if (carry != 0) left->words[left->length++] = carry;
347}
348
353static PRISM_FORCE_INLINE void
354pm_bignum_radix_multiply_bignum(uint64_t base, pm_bignum_t *result, const pm_bignum_t *left, const pm_bignum_t *right, pm_bignum_scratch_t *scratch) {
355 uint32_t length = left->length + right->length;
356 pm_bignum_radix_multiply(base, result->words, left->words, left->length, right->words, right->length, scratch);
357
358 while (length > 0 && result->words[length - 1] == 0) length--;
359 result->length = length;
360}
361
366void
367pm_bignum_add(pm_bignum_t *left, const pm_bignum_t *right) {
368 pm_bignum_radix_add_bignum(PM_BIGNUM_BINARY_BASE, left, right);
369}
370
376void
377pm_bignum_multiply(pm_bignum_t *result, const pm_bignum_t *left, const pm_bignum_t *right, pm_bignum_scratch_t *scratch) {
378 pm_bignum_radix_multiply_bignum(PM_BIGNUM_BINARY_BASE, result, left, right, scratch);
379}
380
385void
386pm_bignum_decimal_add(pm_bignum_t *left, const pm_bignum_t *right) {
387 pm_bignum_radix_add_bignum(PM_BIGNUM_DECIMAL_BASE, left, right);
388}
389
395void
396pm_bignum_decimal_multiply(pm_bignum_t *result, const pm_bignum_t *left, const pm_bignum_t *right, pm_bignum_scratch_t *scratch) {
397 pm_bignum_radix_multiply_bignum(PM_BIGNUM_DECIMAL_BASE, result, left, right, scratch);
398}
#define PRISM_FORCE_INLINE
Force a function to be inlined at every call site.