Files
gookit-goutil/PR.md
T
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

2.3 KiB

fix(strutil): BaseConv family returns "0" for zero instead of empty string

Problem

Converting the value zero through the base-conversion helpers returns an empty string instead of "0":

strutil.BaseConv("0", 10, 16)   // "" , want "0"
strutil.BaseConv("0", 16, 10)   // "" , want "0"
strutil.Base10Conv("0", 62)     // "" , want "0"
strutil.BaseConvInt(0, 64)      // "" , want "0"

This is also internally inconsistent: BaseConvInt returns the correct "0" for small bases because it delegates to strconv.FormatUint/big.Int.Text, but returns "" for bases in (62, 64], which go through the template path:

strutil.BaseConvInt(0, 16)      // "0"  (strconv path)
strutil.BaseConvInt(0, 64)      // ""   (template path)

Root cause

strutil/convbase.go, BaseConvIntByTpl:

func BaseConvIntByTpl(dec uint64, toBase string) string {
	var res string
	toLen := uint64(len(toBase))
	for dec > 0 {                 // never runs when dec == 0
		res = string(toBase[dec%toLen]) + res
		dec /= toLen
	}
	return res                    // returns "" for dec == 0
}

The loop condition dec > 0 is never entered when dec == 0, so the function returns the zero-value empty string. Every public entry point that reaches the template path (BaseConvIntByTpl, BaseConvByTpl, BaseConv, Base10Conv, BaseConvInt for base > 62) inherits the defect.

Fix

Handle the zero case explicitly by emitting the zero digit of the target base (the first character of the template), and guard against an empty template to avoid an out-of-range index:

if toLen == 0 {
	return res
}
if dec == 0 {
	return string(toBase[0])
}

Test

Added TestBaseConv_zero in strutil/convbase_test.go covering BaseConv, Base10Conv, BaseConvInt (small and large bases) and BaseConvIntByTpl.

  • Gate: go test ./strutil/ -run 'TestBaseConv|TestBase10Conv' -> ok (PASS)
  • go vet ./strutil/ -> exit 0
  • go build ./... -> exit 0

Counterfactual: reverting the change in BaseConvIntByTpl makes the new test fail with actual: string("") vs expect: string("0").

Note: TestDateSNv3/dateLen4_base62 in strutil/gensn_test.go is flaky and already fails on the unmodified base commit (it asserts uniqueness of randomly generated serial numbers over 60 iterations); it is unrelated to this change.