Files
dualfroz d6c91f3cdd fix(strutil): return zero digit instead of empty string in base conversion
BaseConvIntByTpl returned an empty string for the value zero because its
loop condition (dec > 0) was never entered. This affected BaseConv,
Base10Conv and BaseConvInt for bases above 62, and was inconsistent with
BaseConvInt for smaller bases which correctly returned "0" via strconv.

Emit the zero digit of the target base for a zero input and guard against
an empty template.
2026-09-04 09:00:39 +02:00

119 lines
2.7 KiB
Go

package strutil
import (
"math/big"
"strconv"
"strings"
"github.com/gookit/goutil/x/basefn"
)
//
// -------------------- convert base --------------------
//
const (
Base10Chars = "0123456789"
Base16Chars = "0123456789abcdef"
Base32Chars = "0123456789abcdefghjkmnpqrstvwxyz"
Base36Chars = "0123456789abcdefghijklmnopqrstuvwxyz"
Base48Chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL"
Base62Chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
Base64Chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/"
)
// Base10Conv convert base10 string to new base string.
func Base10Conv(src string, to int) string { return BaseConv(src, 10, to) }
// BaseConv convert base string by from and to base.
//
// Note: from and to base must be in [2, 64]
//
// Usage:
//
// BaseConv("123", 10, 16) // Output: "7b"
// BaseConv("7b", 16, 10) // Output: "123"
func BaseConv(src string, from, to int) string {
if from > 64 || from < 2 {
from = 10
}
if to > 64 || to < 2 {
to = 16
}
return BaseConvByTpl(src, Base64Chars[:from], Base64Chars[:to])
}
// BaseConvInt convert base int to new base string.
//
// Usage:
//
// BaseConv(123, 16) // Output: "7b"
func BaseConvInt(src uint64, toBase int) string {
if toBase > 64 || toBase < 2 {
toBase = 16
}
// bigInt 支持 2-62 进制转换处理 TODO
if toBase <= 36 {
return strconv.FormatUint(src, toBase)
}
if toBase <= 62 {
bigInt := new(big.Int).SetUint64(src)
return bigInt.Text(toBase)
}
return BaseConvIntByTpl(src, Base64Chars[:toBase])
}
// BaseConvByTpl convert base string by template.
//
// Usage:
//
// BaseConvert("123", Base62Chars, Base16Chars) // Output: "1e"
// BaseConvert("1e", Base16Chars, Base62Chars) // Output: "123"
func BaseConvByTpl(src string, fromBase, toBase string) string {
if fromBase == toBase {
return src
}
// convert to base 10
var dec uint64
if fromBase == Base10Chars {
var err error
dec, err = strconv.ParseUint(src, 10, 0)
if err != nil {
basefn.Panicf("input is not a valid decimal number: %s(%v)", src, err)
}
} else {
fLen := uint64(len(fromBase))
for _, c := range src {
dec = dec*fLen + uint64(strings.IndexRune(fromBase, c))
}
}
// convert to new base
return BaseConvIntByTpl(dec, toBase)
}
// BaseConvIntByTpl convert base int to new base string.
func BaseConvIntByTpl(dec uint64, toBase string) string {
// convert to new base
var res string
toLen := uint64(len(toBase))
if toLen == 0 {
return res
}
// zero maps to the zero digit of the target base, otherwise the loop below
// would produce an empty string for a valid input.
if dec == 0 {
return string(toBase[0])
}
for dec > 0 {
res = string(toBase[dec%toLen]) + res
dec /= toLen
}
return res
}