For a start, this doesn't need to be recursive. It can be a loop. Once
you think of it this way, it makes sense to create temporary big.Rats
that are allocated only once. The design of big allows you to manage
memory allocation so that mathematical operations can be as fast as
possible.
Here's an alternate implementation that I think is pretty easy to read:
package main
import (
"big"
"fmt"
)
func main() {
fmt.Println(SqrtBig(big.NewRat(100.0, 1.0)))
}
func SqrtBig(x *big.Rat) *big.Rat {
z := big.NewRat(10.0, 1.0) // TODO: pick better starting point
a := big.NewRat(0, 0) // temporary
num, den, quo := big.NewRat(0, 0), big.NewRat(0, 0), big.NewRat(0, 0)
for i := 10; i > 0; i-- {
a.Mul(z, z)
num.Sub(a, x)
den.Mul(big.NewRat(2.0, 1.0), z)
quo.Quo(num, den)
a.Sub(z, quo)
z.Set(a)
}
return z
}
Andrew
Just noticed: this big.NewRat(2.0, 1.0) should be created outside the loop.
Andrew
I agree that big's interface could be better, but I don't really
understand this particular complaint. To me, this is kind of like
complaining about the statement
z = x + y
because z's old value is lost. If you want to keep z's current value,
then don't assign it a new one. Similarly, if you want to keep the
value of a big.Int, then don't call any of its destructive functions.
The analogous statement with big is
z.Add(x, y)
Note that it's not necessary to assign the return of this to anything
because it's just returning z. In fact, to assign it to anything other
than z would be misleading. Maybe that's what you're complaining
about, and if that's the case, I can understand. An easy way of
improving big's interface might be to make Add and friends have no
return at all.
If you're suggesting that every arithmetic function return a newly
allocated big.Int (or big.Rat), there's some history there. big
replaces a package called bignum, which did return a new object for
every function called. It was also significantly slower because the
caller had no way of preventing unnecessary allocations.
- Evan
z.Add(x, y)
Note that it's not necessary to assign the return of this to anything
because it's just returning z. In fact, to assign it to anything other
than z would be misleading
I think you're misunderstanding what I'm saying. I realize that Add is
a destructive function. I'm saying that writing
r := z.Add(x, y)
is misleading because it's hard to tell that the value of z is
changing in this statement.
What I'm telling you is that if you don't want to lose the value of z,
then don't bring z into the picture at all. Just say
r := new(big.Int).Add(x, y)
- Evan
It's not in this case. If you want to keep around all your old values,
then you have no choice but to allocate new ones. The real benefit is
when you *don't* need to keep around your old values. If I don't care
about the old value of z. I can reuse its storage and avoid an
allocation.
The bignum interface, on the other hand, would give the caller no
choice and allocate every time.
- Evan
a.Add(b); // a = a + b -> No return value.
--
Regards,
-- Clark
That would probably be better than what's there right now. However,
you might consider returning a pointer to a to allow things like this:
a.Add(b).Mul(c)
Then you have the same confusion that's there now, of course.
- Evan
It would be an extremely explicit API, but in the cases you need bignum
(such as implementing RSA), being explicit is often better than having
subtleties.
--
Regards,
-- Clark
Like Evan said, z.Something(x, y) should be thought of as
z = x Something y
If z appears on the left side, it gets overwritten.
If it appears on the right, it does not.
It's a familiar model for many programmers.
The claim that you have to use a dummy value
to remember z's original value is hard to understand:
the same is true of ordinary assignment. (If even that
bothers you, you might look into Haskell instead of Go.)
As for why you don't write
z.Equals(x.Something(y))
the answer is that this style generates garbage at
an amazing rate, and for any serious use of bignums
(like crypto) you need to control that rate to get reasonable
performance. There was a package with that interface,
called "bignum" and then "exp/bignum". It was too slow.
You can find it in the repository history if you're curious
to play with it.
> If anything, it's just hideous (almost to the point of obfuscated) code
> where it could be much cleaner.
Don't take this the wrong way, but just because you wrote
some hideous code doesn't mean that it's impossible to
write nice code. Almost everyone who starts programming
with big (myself included) starts out with hideous code,
because it takes a while to internalize "write the assignments
and translate to method calls".
If nothing else, instead of writing
a := temp.Mul(z, z)
a = temp.Mul(x, y)
it's almost always better to write
a := new(big.Rat).Mul(z, z)
a.Mul(x, y)
(The two lines have different translations because of := vs =.)
That is, treat each big.Rat (or big.Int) as its own variable and
only ever use one of those two forms and you'll get readable
code without the kinds of subtle bugs that you pointed out.
Russ
what optimization are you thinking of here?
FWIW, a.Duplicate() would be equivalent to:
new(big.Int).Set(a)
in the current API.
BTW, i don't understand what the problem is with
a.Add(a, b)
So add a Duplicate method as well and then you can write:
a.Duplicate().Add(b).Mul(c)
Or maybe use some closure magic:
a.UseInvariant(func(i *bigint) *bigint { return i.Add(b).Mul(c) })
where UseInvariant would create a copy of a and pass that into the closure.
I think I'll have to play with that latter idea in GoLightly as there are quite a few places where I use Duplicate() which might work nicer with UseInvariant() - although the method could probably do with a better name...
Ellie
Eleanor McHugh
Games With Brains
http://feyeleanor.tel
----
raise ArgumentError unless @reality.responds_to? :reason
I believe a.Add(a, b) already does this.
Russ
Use the FloatString function:
http://golang.org/pkg/big/#Rat.FloatString
Andrew