Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Some optimizations #58

Merged
merged 5 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 49 additions & 58 deletions base57.go
Original file line number Diff line number Diff line change
@@ -1,91 +1,82 @@
package shortuuid

import (
"encoding/binary"
"fmt"
"math"
"math/big"
"strings"

"github.com/google/uuid"
"math/bits"
"strings"
)

type base57 struct {
// alphabet is the character set to construct the UUID from.
alphabet alphabet
}

const (
strLen = 22
alphabetLen = 57
)

// Encode encodes uuid.UUID into a string using the most significant bits (MSB)
// first according to the alphabet.
func (b base57) Encode(u uuid.UUID) string {
var num big.Int
num.SetString(strings.Replace(u.String(), "-", "", 4), 16)

// Calculate encoded length.
length := math.Ceil(math.Log(math.Pow(2, 128)) / math.Log(float64(b.alphabet.Length())))

return b.numToString(&num, int(length))
}

// Decode decodes a string according to the alphabet into a uuid.UUID. If s is
// too short, its most significant bits (MSB) will be padded with 0 (zero).
func (b base57) Decode(u string) (uuid.UUID, error) {
str, err := b.stringToNum(u)
if err != nil {
return uuid.Nil, err
num := uint128{
binary.BigEndian.Uint64(u[8:]),
binary.BigEndian.Uint64(u[:8]),
}
return uuid.Parse(str)
}

// numToString converts a number a string using the given alphabet.
func (b *base57) numToString(number *big.Int, padToLen int) string {
var (
out []rune
digit *big.Int
)
var outIndexes [strLen]uint64

alphaLen := big.NewInt(b.alphabet.Length())

zero := new(big.Int)
for number.Cmp(zero) > 0 {
number, digit = new(big.Int).DivMod(number, alphaLen, new(big.Int))
out = append(out, b.alphabet.chars[digit.Int64()])
for i := strLen - 1; num.Hi > 0 || num.Lo > 0; i-- {
num, outIndexes[i] = num.quoRem64(alphabetLen)
}

if padToLen > 0 {
remainder := math.Max(float64(padToLen-len(out)), 0)
out = append(out, []rune(strings.Repeat(string(b.alphabet.chars[0]), int(remainder)))...)
var sb strings.Builder
sb.Grow(strLen)
for i := 0; i < strLen; i++ {
sb.WriteRune(b.alphabet.chars[outIndexes[i]])
}

reverse(out)

return string(out)
return sb.String()
}

// stringToNum converts a string a number using the given alphabet.
func (b *base57) stringToNum(s string) (string, error) {
n := big.NewInt(0)
// Decode decodes a string according to the alphabet into a uuid.UUID. If s is
// too short, its most significant bits (MSB) will be padded with 0 (zero).
func (b base57) Decode(s string) (u uuid.UUID, err error) {
var n uint128
var index int64

for _, char := range s {
n.Mul(n, big.NewInt(b.alphabet.Length()))

index, err := b.alphabet.Index(char)
index, err = b.alphabet.Index(char)
if err != nil {
return "", err
return
}
n, err = n.mulAdd64(alphabetLen, uint64(index))
if err != nil {
return
}

n.Add(n, big.NewInt(index))
}
binary.BigEndian.PutUint64(u[:8], n.Hi)
binary.BigEndian.PutUint64(u[8:], n.Lo)
return
}

if n.BitLen() > 128 {
return "", fmt.Errorf("number is out of range (need a 128-bit value)")
}
type uint128 struct {
Lo, Hi uint64
}

return fmt.Sprintf("%032x", n), nil
func (u uint128) quoRem64(v uint64) (q uint128, r uint64) {
q.Hi, r = bits.Div64(0, u.Hi, v)
q.Lo, r = bits.Div64(r, u.Lo, v)
return
}

// reverse reverses a inline.
func reverse(a []rune) {
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
a[i], a[j] = a[j], a[i]
func (u uint128) mulAdd64(m uint64, a uint64) (uint128, error) {
hi, lo := bits.Mul64(u.Lo, m)
p0, p1 := bits.Mul64(u.Hi, m)
lo, c0 := bits.Add64(lo, a, 0)
hi, c1 := bits.Add64(hi, p1, c0)
if p0 != 0 || c1 != 0 {
return uint128{}, fmt.Errorf("number is out of range (need a 128-bit value)")
}
return uint128{lo, hi}, nil
}
13 changes: 0 additions & 13 deletions base57_test.go

This file was deleted.

2 changes: 1 addition & 1 deletion shortuuid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,6 @@ func BenchmarkEncoding(b *testing.B) {

func BenchmarkDecoding(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = DefaultEncoder.Decode("c3eeb3e6-e577-4de2-b5bb-08371196b453")
_, _ = DefaultEncoder.Decode("nUfojcH2M5j9j3Tk5A8mf7")
}
}
Loading