Actually, both implementations have bugs!

The recursive implementation ends with:
```
m := (a + b) / 2
return z.mul(nat(nil).mulRange(a, m), nat(nil).mulRange(m+1, b))
```

That's a bug whenever `(a+b)` overflows, making `m` small.
FIX: `m := a + (b-a)/2`

My iterative implementation went into an infinite loop here:
`for m := a + 1; m <= b; m++ {`
if b is `math.MaxUint64`
FIX: add `&& m > a` to the exit condition is an easy fix, but pays a small
penalty for the vast majority of calls that don't have b=MaxUint64

I would add these to `mulRangesN` of the unit test:
```
 {math.MaxUint64 - 3, math.MaxUint64 - 1,
"6277101735386680760773248120919220245411599323494568951784"},
{math.MaxUint64 - 3, math.MaxUint64,
"115792089237316195360799967654821100226821973275796746098729803619699194331160"}
```

On Sun, Jan 7, 2024 at 6:34 AM John Jannotti <janno...@gmail.com> wrote:

> I'm equally curious.
>
> FWIW, I realized the loop should perhaps be
> ```
> mb := nat(nil).setUint64(b) // ensure mb starts big enough for b, even on
> 32-bit arch
> for m := a + 1; m <= b; m++ {
>   mb.setUint64(m)
>   z = z.mul(z, mb)
> }
> ```
> to avoid allocating repeatedly for `m`, which yields:
> BenchmarkIterativeMulRangeN-10      354685      3032 ns/op    2129 B/op
>    48 allocs/op
>
> On Sun, Jan 7, 2024 at 2:41 AM Rob Pike <r...@golang.org> wrote:
>
>> It seems reasonable but first I'd like to understand why the recursive
>> method is used. I can't deduce why, but the CL that adds it, by gri, does
>> Karatsuba multiplication, which implies something deep is going on. I'll
>> add him to the conversation.
>>
>> -rob
>>
>>
>>
>>
>> On Sun, Jan 7, 2024 at 5:46 PM John Jannotti <janno...@gmail.com> wrote:
>>
>>> I enjoy bignum implementations, so I was looking through nat.go and saw
>>> that `mulRange` is implemented in a surprising, recursive way,.  In the
>>> non-base case, `mulRange(a, b)` returns `mulrange(a, (a+b)/2) *
>>> mulRange(1+(a+b)/2, b)` (lots of big.Int ceremony elided).
>>>
>>> That's fine, but I didn't see any advantage over the straightforward
>>> (and simpler?) for loop.
>>>
>>> ```
>>> z = z.setUint64(a)
>>> for m := a + 1; m <= b; m++ {
>>> z = z.mul(z, nat(nil).setUint64(m))
>>> }
>>> return z
>>> ```
>>>
>>> In fact, I suspected the existing code was slower, and allocated a lot
>>> more.  That seems true. A quick benchmark, using the existing unit test as
>>> the benchmark, yields
>>> BenchmarkRecusiveMulRangeN-10       169417       6856 ns/op     9452
>>> B/op      338 allocs/op
>>> BenchmarkIterativeMulRangeN-10       265354       4269 ns/op     2505
>>> B/op      196 allocs/op
>>>
>>> I doubt `mulRange` is a performance bottleneck in anyone's code! But it
>>> is exported as `int.MulRange` so I guess it's viewed with some value.  And
>>> seeing as how the for-loop seems even easier to understand that the
>>> recursive version, maybe it's worth submitting a PR? (If so, should I
>>> create an issue first?)
>>>
>>>
>>>
>>> --
>>> You received this message because you are subscribed to the Google
>>> Groups "golang-nuts" group.
>>> To unsubscribe from this group and stop receiving emails from it, send
>>> an email to golang-nuts+unsubscr...@googlegroups.com.
>>> To view this discussion on the web visit
>>> https://groups.google.com/d/msgid/golang-nuts/e6ceb75a-f8b7-4f77-97dc-9445fb750782n%40googlegroups.com
>>> <https://groups.google.com/d/msgid/golang-nuts/e6ceb75a-f8b7-4f77-97dc-9445fb750782n%40googlegroups.com?utm_medium=email&utm_source=footer>
>>> .
>>>
>>

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/CALZHwko9GC7C_JThfVdFxBQtCmu9%2B%2BNYun8Tope4608oiio_6g%40mail.gmail.com.

Reply via email to