OldCurmudgeon
OldCurmudgeon

Reputation: 65793

Acessing unexported function `nat.string`

I want to convert a big.Int to simple base32. Not the standard base32 stuff like the RFC 4648 implemented by base32 nor zBase32 nor Crockford I want just simple normal 5-bits per character 0-9A-V character set.

I am aware of the base32 package and it does not do what I want - it builds the result in a standard base 32 number with padding and stuff I don't want. Certainly I could use it and tear off the trailing "=" characters and hack what remains but that just seems like a brutal solution.

There is a big.SetString(string, base) that can parse a base32 number in string form but there is no reverse - which is what I am really looking for, a big.GetString(base) like the Java BigInteger.toString(int base).

There is, however, a nat.string which does exactly what I want. How can I gain access to it?

Is there a way I could manually extend big to implement big.GetString(base) which trivially calls nat.string with the correct charset?

Is there a way I can reach into the unexported big.nat which big uses and call nat.string?

Is there something else I can do?

P.S. I'd also be interested in using nat.trailingZeroBits - I had to write my own because I didn't realise this was already done.

Upvotes: 2

Views: 158

Answers (3)

OldCurmudgeon
OldCurmudgeon

Reputation: 65793

I have posted on the go-nuts thread. They seem happy with my suggestion.

Meanwhile - my implementation of Base32 (does NOT handle sign - like nat)

// The digits
const charset string = "0123456789ABCDEFGHIJKLMNOPQRSTUV"

/*
 Convert the big to base32
*/
func Base32(n big.Int) string {
    // The bytes of the number
    bytes := n.Bytes()
    nBytes := len(bytes)
    if nBytes == 0 {
        // Special case 0.
        return "0"
    }
    // How many digits will be in the string?
    nBits := nBytes * 8
    nDigits := nBits / 5
    if nBits%5 > 0 {
        nDigits += 1
    }
    // Grow the digits into an array
    digits := make([]byte, nDigits)
    digit := nDigits - 1
    // Peel off 5 bits from the array each time.
    for b, shr := nBytes-1, uint(0); b >= 0; {
        // The next lot is there.
        shrp5 := shr + 5
        // Build my bits.
        x := (bytes[b] >> shr)
        if shrp5 > 8 && b > 0 {
            of := shrp5 - 8
            // Add in some bits from the next byte too.
            x &= (1 << (8 - shr)) - 1
            x |= bytes[b-1] << (5 - of)
        }
        // Shift 5 more.
        shr = shrp5
        // Should we step to next byte?
        if shr >= 8 {
            // Next byte
            shr -= 8
            b -= 1
        }
        x &= 0x1f
        // Build my digit
        digits[digit] = charset[x]
        digit -= 1
    }
    // Skip leading zeros.
    lz := 0
    for digits[lz] == '0' {
        lz += 1
    }
    // Return the string equivalent.
    return string(digits[lz:])
}

Comments welcome.

Upvotes: 1

Nick Craig-Wood
Nick Craig-Wood

Reputation: 54079

I don't think the go team will ever export anything in nat as that is an implementation details for big.Int. They may look kindly upon a request to export a ToBase function though from big.Int.

In the mean time here is a lightly tested naive base converter for you

func ToBase(x *big.Int, base int) string {
    if x.Sign() == 0 {
        return "0"
    }
    y := new(big.Int).Set(x)
    b := big.NewInt(int64(base))
    charset := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    out := make([]byte, 0, 16)
    negative := false
    if y.Sign() < 0 {
        negative = true
        y.Neg(y)
    }
    digit := new(big.Int)
    for y.Sign() != 0 {
        y.DivMod(y, b, digit)
        out = append(out, charset[digit.Int64()])
    }
    if negative {
        out = append(out, '-')
    }
    // Reverse out
    for i, j := 0, len(out)-1; i < j; i, j = i+1, j-1 {
        out[i], out[j] = out[j], out[i]
    }
    return string(out)
}

Playground link

Upvotes: 2

Intermernet
Intermernet

Reputation: 19378

You can't access unexported functions at all. You'd need to re-write at least some portion of nat.go in order to achieve that functionality. Some of those functions look very useful, so it may be worth sending a feature request to the golang-nuts group asking for some of them to be exported in a future release.

You can however use strconv.FormatInt() to do what you require.

given a big.Int b you can do:

strconv.FormatInt(b.Int64(), 32)

Full example:

package main

import (
    "fmt"
    "math/big"
    "strconv"
)

func main() {
    i := 3286583923486565782 // Some random integer
    b := big.NewInt(int64(i))
    fmt.Println(strconv.FormatInt(b.Int64(), 32))
}

Produces:

2r72al99uq9cm

Playground

Upvotes: 2

Related Questions