Skip to content

Commit

Permalink
implement Log10 and CmpUint64 (#136)
Browse files Browse the repository at this point in the history
Adds the following methods

```golang
// CmpUint64 compares z and x and returns:
//
//	-1 if z <  x
//	 0 if z == x
//	+1 if z >  x
func (z *Int) CmpUint64(n uint64) int 

// Log10 returns the log in base 10, floored to nearest integer.
// **OBS** This method returns '0' for '0', not `-Inf`.
func (z *Int) Log10() uint
```
The implementation of `Log10` is probably close to the optimal way. The majority of cases are handled by one lookup based on the bitlength. 

For the remaining cases, one an additional lookup is performed, a comparison, and then it is done. 

This PR adds around 2K persistent memory for lookup tables and precalculated integers. 
```
lut [257] uint8 // 257 B
pows = [60]Int         // 1920 B 
pows64 = [20]uint64 // 20*8 = 160 B
```

Ref: #123
  • Loading branch information
holiman authored Mar 31, 2023
1 parent 1c9ad56 commit e42b95a
Show file tree
Hide file tree
Showing 2 changed files with 177 additions and 0 deletions.
67 changes: 67 additions & 0 deletions uint256.go
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,21 @@ func (z *Int) Cmp(x *Int) (r int) {
return 1
}

// CmpUint64 compares z and x and returns:
//
// -1 if z < x
// 0 if z == x
// +1 if z > x
func (z *Int) CmpUint64(x uint64) int {
if z[0] > x || (z[1]|z[2]|z[3]) != 0 {
return 1
}
if z[0] == x {
return 0
}
return -1
}

// CmpBig compares z and x and returns:
//
// -1 if z < x
Expand Down Expand Up @@ -1321,3 +1336,55 @@ func (z *Int) Sqrt(x *Int) *Int {
z1, z2 = z2, z1
}
}

var (
// lut is a lookuptable of bitlength -> log10, used in Log10().
lut = [257]int8{0, 0, 0, 0, -1, 1, 1, -2, 2, 2, -3, 3, 3, 3, -4, 4, 4, -5, 5, 5, -6, 6, 6, 6, -7, 7, 7, -8, 8, 8, -9, 9, 9, 9, -10, 10, 10, -11, 11, 11, -12, 12, 12, 12, -13, 13, 13, -14, 14, 14, -15, 15, 15, 15, -16, 16, 16, -17, 17, 17, -18, 18, 18, 18, -19, 19, 19, -20, 20, 20, -21, 21, 21, 21, -22, 22, 22, -23, 23, 23, -24, 24, 24, 24, -25, 25, 25, -26, 26, 26, -27, 27, 27, 27, -28, 28, 28, -29, 29, 29, -30, 30, 30, -31, 31, 31, 31, -32, 32, 32, -33, 33, 33, -34, 34, 34, 34, -35, 35, 35, -36, 36, 36, -37, 37, 37, 37, -38, 38, 38, -39, 39, 39, -40, 40, 40, 40, -41, 41, 41, -42, 42, 42, -43, 43, 43, 43, -44, 44, 44, -45, 45, 45, -46, 46, 46, 46, -47, 47, 47, -48, 48, 48, -49, 49, 49, 49, -50, 50, 50, -51, 51, 51, -52, 52, 52, 52, -53, 53, 53, -54, 54, 54, -55, 55, 55, 55, -56, 56, 56, -57, 57, 57, -58, 58, 58, -59, 59, 59, 59, -60, 60, 60, -61, 61, 61, -62, 62, 62, 62, -63, 63, 63, -64, 64, 64, -65, 65, 65, 65, -66, 66, 66, -67, 67, 67, -68, 68, 68, 68, -69, 69, 69, -70, 70, 70, -71, 71, 71, 71, -72, 72, 72, -73, 73, 73, -74, 74, 74, 74, -75, 75, 75, -76, 76, 76, -77}

// pows64 contains 10^0 ... 10^19
pows64 = [20]uint64{
1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19,
}
// pows contain 1 ** 20 ... 10 ** 80
pows = [60]Int{
Int{7766279631452241920, 5, 0, 0}, Int{3875820019684212736, 54, 0, 0}, Int{1864712049423024128, 542, 0, 0}, Int{200376420520689664, 5421, 0, 0}, Int{2003764205206896640, 54210, 0, 0}, Int{1590897978359414784, 542101, 0, 0}, Int{15908979783594147840, 5421010, 0, 0}, Int{11515845246265065472, 54210108, 0, 0}, Int{4477988020393345024, 542101086, 0, 0}, Int{7886392056514347008, 5421010862, 0, 0}, Int{5076944270305263616, 54210108624, 0, 0}, Int{13875954555633532928, 542101086242, 0, 0}, Int{9632337040368467968, 5421010862427, 0, 0},
Int{4089650035136921600, 54210108624275, 0, 0}, Int{4003012203950112768, 542101086242752, 0, 0}, Int{3136633892082024448, 5421010862427522, 0, 0}, Int{12919594847110692864, 54210108624275221, 0, 0}, Int{68739955140067328, 542101086242752217, 0, 0}, Int{687399551400673280, 5421010862427522170, 0, 0}, Int{6873995514006732800, 17316620476856118468, 2, 0}, Int{13399722918938673152, 7145508105175220139, 29, 0}, Int{4870020673419870208, 16114848830623546549, 293, 0}, Int{11806718586779598848, 13574535716559052564, 2938, 0},
Int{7386721425538678784, 6618148649623664334, 29387, 0}, Int{80237960548581376, 10841254275107988496, 293873, 0}, Int{802379605485813760, 16178822382532126880, 2938735, 0}, Int{8023796054858137600, 14214271235644855872, 29387358, 0}, Int{6450984253743169536, 13015503840481697412, 293873587, 0}, Int{9169610316303040512, 1027829888850112811, 2938735877, 0}, Int{17909126868192198656, 10278298888501128114, 29387358770, 0}, Int{13070572018536022016, 10549268516463523069, 293873587705, 0}, Int{1578511669393358848, 13258964796087472617, 2938735877055, 0}, Int{15785116693933588480, 3462439444907864858, 29387358770557, 0},
Int{10277214349659471872, 16177650375369096972, 293873587705571, 0}, Int{10538423128046960640, 14202551164014556797, 2938735877055718, 0}, Int{13150510911921848320, 12898303124178706663, 29387358770557187, 0}, Int{2377900603251621888, 18302566799529756941, 293873587705571876, 0}, Int{5332261958806667264, 17004971331911604867, 2938735877055718769, 0}, Int{16429131440647569408, 4029016655730084128, 10940614696847636083, 1}, Int{16717361816799281152, 3396678409881738056, 17172426599928602752, 15}, Int{1152921504606846976, 15520040025107828953, 5703569335900062977, 159}, Int{11529215046068469760, 7626447661401876602, 1695461137871974930, 1593}, Int{4611686018427387904, 2477500319180559562, 16954611378719749304, 15930}, Int{9223372036854775808, 6328259118096044006, 3525417123811528497, 159309},
Int{0, 7942358959831785217, 16807427164405733357, 1593091}, Int{0, 5636613303479645706, 2053574980671369030, 15930919}, Int{0, 1025900813667802212, 2089005733004138687, 159309191}, Int{0, 10259008136678022120, 2443313256331835254, 1593091911}, Int{0, 10356360998232463120, 5986388489608800929, 15930919111}, Int{0, 11329889613776873120, 4523652674959354447, 159309191113}, Int{0, 2618431695511421504, 8343038602174441244, 1593091911132}, Int{0, 7737572881404663424, 9643409726906205977, 15930919111324}, Int{0, 3588752519208427776, 4200376900514301694, 159309191113245}, Int{0, 17440781118374726144, 5110280857723913709, 1593091911132452}, Int{0, 8387114520361296896, 14209320429820033867, 15930919111324522}, Int{0, 10084168908774762496, 12965995782233477362, 159309191113245227}, Int{0, 8607968719199866880, 532749306367912313, 1593091911132452277}, Int{0, 12292710897160462336, 5327493063679123134, 15930919111324522770}, Int{0, 12246644529347313664, 16381442489372128114, 11735238523568814774}, Int{0, 11785980851215826944, 16240472304044868218, 6671920793430838052},
}
)

// Log10 returns the log in base 10, floored to nearest integer.
// **OBS** This method returns '0' for '0', not `-Inf`.
func (z *Int) Log10() uint {
// For some bit-lengths, there's only one possible value. Example:
// three bits can only represent [100 ... 111], or [4 ... 7]
// Ergo, bitlen:3 -> log10 == 0
res := lut[z.BitLen()%257]
if res >= 0 {
return uint(res)
}
// It was negative, which is a signal that we need to do one more check
// do determine which log it is. First remove the negation
res = -res

// We now lookup via the power of tens. Example:
// bitlen 4, [1000 ... 1111], or [8 .. 15]
// In order to figure out if it is '0' or '1', we only need to do one comparison,
// is it larger or smaller than '10'?

// For bitlengths < 20, we can use the uint64-space
if res < 20 {
// Uint64-space
if z.CmpUint64(pows64[res]) < 0 {
return uint(res - 1)
}
return uint(res)
}
// Non-uint64 space
if z.Cmp(&pows[res-20]) < 0 {
return uint(res - 1)
}
return uint(res)
}
110 changes: 110 additions & 0 deletions uint256_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"crypto/rand"
"encoding/hex"
"fmt"
"math"
"math/big"
"testing"
)
Expand Down Expand Up @@ -1651,6 +1652,115 @@ func TestByte32Representation(t *testing.T) {
}
}

func testLog10(t *testing.T, z *Int) {
want := uint(len(z.Dec()))
if want > 0 {
want--
}
if have := z.Log10(); have != want {
t.Errorf("%s (%s): have %v want %v", z.Hex(), z.Dec(), have, want)
}
}

func TestLog10(t *testing.T) {
for i := uint(0); i < 255; i++ {
z := NewInt(1)
testLog10(t, z.Lsh(z, i))
}
z := NewInt(1)
ten := NewInt(10)
for i := uint(0); i < 80; i++ {
testLog10(t, z.Mul(z, ten))
}
}

func FuzzLog10(f *testing.F) {
f.Fuzz(func(t *testing.T, aa, bb, cc, dd uint64) {
testLog10(t, &Int{aa, bb, cc, dd})
})
}

func BenchmarkLog10(b *testing.B) {
var u256Ints []*Int
var bigints []*big.Int

for i := uint(0); i < 255; i++ {
a := NewInt(1)
a.Lsh(a, i)
u256Ints = append(u256Ints, a)
bigints = append(bigints, a.ToBig())
}
b.Run("Log10/uint256", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for _, z := range u256Ints {
_ = z.Log10()
}
}
})
b.Run("Log10/big", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for _, z := range bigints {
f, _ := new(big.Float).SetInt(z).Float64()
_ = int(math.Log10(f))
}
}
})
}

func testCmpU64(t *testing.T, z *Int, x uint64) {
want := z.ToBig().Cmp(new(big.Int).SetUint64(x))
have := z.CmpUint64(x)
if have != want {
t.Errorf("%s.CmpUint64( %x ) : have %v want %v", z.Hex(), x, have, want)
}
}

func TestCmpUint64(t *testing.T) {
for i := uint(0); i < 255; i++ {
z := NewInt(1)
z.Lsh(z, i)
// z, z
testCmpU64(t, z, new(Int).Set(z).Uint64())
// z, z + 1
testCmpU64(t, z, new(Int).AddUint64(z, 1).Uint64())
// z, z - 1
testCmpU64(t, z, new(Int).SubUint64(z, 1).Uint64())
// z, z >> 64
testCmpU64(t, z, new(big.Int).Rsh(new(Int).Set(z).ToBig(), 64).Uint64())
}
}

//func TestLogBit(t *testing.T) {
//
// a := big.NewInt(1)
// b := big.NewInt(1)
// m := big.NewInt(10)
// last := 0
// fmt.Printf("lut = [257]int{")
// for i := 0; i < 77; i++ {
// a.Mul(a, m)
// b.Sub(a, big.NewInt(1))
// for j := last; j < a.BitLen(); j++ {
// fmt.Printf("%d,", i)
// }
// //fmt.Printf("bitlength %d has log %d or %d\n,", a.BitLen(), i, i+1)
// fmt.Printf("%d,", -(i + 1))
// last = a.BitLen() + 1
// }
// fmt.Printf("}\n")
//}
//
//func TestPrintTenPows(t *testing.T) {
// z := NewInt(1)
// ten := NewInt(10)
// for i := uint(0); i < 80; i++ {
// fmt.Printf("pows[%d] = &Int{ %d, %d, %d, %d }\n", i, z[0], z[1], z[2], z[3])
// z.Mul(z, ten)
// }
//}

func testCmpBig(t *testing.T, z *Int, x *big.Int) {
want := z.ToBig().Cmp(x)
have := z.CmpBig(x)
Expand Down

0 comments on commit e42b95a

Please sign in to comment.