Golang 中整数转字符串的方法

2020-01-28 13:29:13王旭

通过循环求余实现。进制的转换也是这种方式。


for u >= b {
  i--
  a[i] = uint(u)&m
  u >>= s
}

上面的代码实现了进制的转换。而 digits[uint(u)&m] 实现了转换后的结果再转成字符。

常规情况


b := uint64(base)
for u >= b {
 i--
 q := u / b
 a[i] = digits[uint(u-q*b)]
 u = q
}
// u < base
i--
a[i] = digits[uint(u)]

依然是循环求余来实现。这段代码更像是给人看的。和上面2的倍数的进制转换的区别在于,上面的代码把除法 / 换成了右移( >> ) s 位,把求余 % 换成了逻辑与 & 操作。

Sprintf 的实现


switch f := arg.(type) {
  case bool:
    p.fmtBool(f, verb)
  case float32:
    p.fmtFloat(float64(f), 32, verb)
  case float64:
    p.fmtFloat(f, 64, verb)
  case complex64:
    p.fmtComplex(complex128(f), 64, verb)
  case complex128:
    p.fmtComplex(f, 128, verb)
  case int:
    p.fmtInteger(uint64(f), signed, verb)
  ...
}

判断类型,如果是整数 int 类型,不需要反射,直接计算。支持的都是基础类型,其它类型只能通过反射实现。

Sprintf 支持的进制只有10 %d 、16 x 、8 o 、2 b 这四种,其它的会包 fmt: unknown base; can't happen 异常。


switch base {
case 10:
 for u >= 10 {
 i--
 next := u / 10
 buf[i] = byte('0' + u - next*10)
 u = next
 }
case 16:
 for u >= 16 {
 i--
 buf[i] = digits[u&0xF]
 u >>= 4
 }
case 8:
 for u >= 8 {
 i--
 buf[i] = byte('0' + u&7)
 u >>= 3
 }
case 2:
 for u >= 2 {
 i--
 buf[i] = byte('0' + u&1)
 u >>= 1
 }
default:
 panic("fmt: unknown base; can't happen")
}

2、8、16进制和之前 FormatInt 差不多,而10进制的性能差一些,每次只能处理一位数字,而不像 FormatInt 一次处理两位。

性能对比


var smallInt = 35
var bigInt = 999999999999999

func BenchmarkItoa(b *testing.B) {
  for i := 0; i < b.N; i++ {
    val := strconv.Itoa(smallInt)
    _ = val
  }
}

func BenchmarkItoaFormatInt(b *testing.B) {
  for i := 0; i < b.N; i++ {
    val := strconv.FormatInt(int64(smallInt), 10)
    _ = val
  }
}

func BenchmarkItoaSprintf(b *testing.B) {
  for i := 0; i < b.N; i++ {
    val := fmt.Sprintf("%d", smallInt)
    _ = val
  }
}

func BenchmarkItoaBase2Sprintf(b *testing.B) {
  for i := 0; i < b.N; i++ {
    val := fmt.Sprintf("%b", smallInt)
    _ = val
  }
}

func BenchmarkItoaBase2FormatInt(b *testing.B) {
  for i := 0; i < b.N; i++ {
    val := strconv.FormatInt(int64(smallInt), 2)
    _ = val
  }
}

func BenchmarkItoaBig(b *testing.B) {
  for i := 0; i < b.N; i++ {
    val := strconv.Itoa(bigInt)
    _ = val
  }
}

func BenchmarkItoaFormatIntBig(b *testing.B) {
  for i := 0; i < b.N; i++ {
    val := strconv.FormatInt(int64(bigInt), 10)
    _ = val
  }
}

func BenchmarkItoaSprintfBig(b *testing.B) {
  for i := 0; i < b.N; i++ {
    val := fmt.Sprintf("%d", bigInt)
    _ = val
  }
}