SoFunction
Updated on 2025-03-05

Methods of converting integers into strings in Golang

Plastic styling is often used to convert strings. This article discusses the methods provided by Golang. Based on go1.10.1


The fmt package should be the most common one. I have been exposed to Golang since I first started learning, and I have to use it when writing ‘hello, world’. It also supports converting formatted variables to strings.

func Sprintf(format string, a ...interface{}) string
Sprintf formats according to a format specifier and returns the resulting string.
("%d", a)

%d represents a decimal integer.


func Itoa(i int) string
Itoa is shorthand for FormatInt(int64(i), 10).
(a)


func FormatInt(i int64, base int) string
FormatInt returns the string representation of i in the given base, for 2 <= base <= 36. The result uses the lower-case letters ‘a' to ‘z' for digit values >= 10.

Parameter i is the integer to be converted, base is in the binary, for example, binary, and supports 2 to hexadecimal.

(int64(a), 10)

Implementation of Format

Two-digit integer of [0, 99)

There is an accelerated optimization algorithm for small (less than or equal to 100) decimal positive integers:

if fastSmalls && 0 <= i && i < nSmalls && base == 10 {
 return small(int(i))
}

The principle of acceleration is to calculate the converted strings of non-negative integers within 100 in advance.

const smallsString = "00010203040506070809" +
 "10111213141516171819" +
 "20212223242526272829" +
 "30313233343536373839" +
 "40414243444546474849" +
 "50515253545556575859" +
 "60616263646566676869" +
 "70717273747576777879" +
 "80818283848586878889" +
 "90919293949596979899"

It can be seen that the conversion results are from 1 to 99, and each result only takes two digits. Of course, the number of individuals has to be handled specially, and the single digit result is only one.

func small(i int) string {
 off := 0
 if i < 10 {
  off = 1
 }
 return smallsString[i*2+off : i*2+2]
}

If the converted number is single digits, the offset becomes 1, and the default is 0.

Only 2 to hexadecimal conversion is supported. Hexadecimal is 10 numbers plus 26 lowercase letters, and it cannot be calculated beyond this range.

var a [64 + 1]byte

The maximum number of plastic surgery is 64 digits, and one is added because there is a symbol. When converting and calculating, it is necessary to divide it into decimal and non-decimal cases.

Decimal conversion

In the decimal system, two-digit conversion, why do you do this? Non-negative integer conversion within 100 can be accelerated by the above special cases. Very interesting.

us := uint(u)
for us >= 100 {
 is := us % 100 * 2
 us /= 100
 i -= 2
 a[i+1] = smallsString[is+1]
 a[i+0] = smallsString[is+0]
}

2, 4, 8, 16, and 32-digit conversion.

const digits = "0123456789abcdefghijklmnopqrstuvwxyz"

var shifts = [len(digits) + 1]uint{
  1 << 1: 1,
  1 << 2: 2,
  1 << 3: 3,
  1 << 4: 4,
  1 << 5: 5,
}

if s := shifts[base]; s > 0 {
 // base is power of 2: use shifts and masks instead of / and %
 b := uint64(base)
 m := uint(base) - 1 // == 1<<s - 1
 for u >= b {
 i--
 a[i] = digits[uint(u)&m]
 u >>= s
 }
 // u < base
 i--
 a[i] = digits[uint(u)]
}

Implemented by looping and finding the remaining. This is also the way to convert the binary system.

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

The above code implements the conversion of the division. digits[uint(u)&m] realizes the conversion result and then converts it into characters.

Regular situation

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)]

It is still a cycle to achieve the rest. This code is more like it is for people to see. The difference between the division conversion of multiples of 2 above is that the above code replaces division / with the right shift ( >> ) s bits, and replaces the balance % with the logic and & operation.

Sprintf implementation

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

To determine the type, if it is an integer int type, no reflection is required, and it is calculated directly. All supported are basic types, and other types can only be implemented through reflection.

Sprintf supports only four types: 10%d, 16 x, 8 o, and 2 b. Others will include fmt: unknown base; can't happen exception.

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. Hexadecimal is similar to the previous FormatInt, while the performance of decimal is worse, and can only process one number at a time, unlike FormatInt that processes two digits at a time.

Performance comparison

var smallInt = 35
var bigInt = 999999999999999

func BenchmarkItoa(b *) {
  for i := 0; i < ; i++ {
    val := (smallInt)
    _ = val
  }
}

func BenchmarkItoaFormatInt(b *) {
  for i := 0; i < ; i++ {
    val := (int64(smallInt), 10)
    _ = val
  }
}

func BenchmarkItoaSprintf(b *) {
  for i := 0; i < ; i++ {
    val := ("%d", smallInt)
    _ = val
  }
}

func BenchmarkItoaBase2Sprintf(b *) {
  for i := 0; i < ; i++ {
    val := ("%b", smallInt)
    _ = val
  }
}

func BenchmarkItoaBase2FormatInt(b *) {
  for i := 0; i < ; i++ {
    val := (int64(smallInt), 2)
    _ = val
  }
}

func BenchmarkItoaBig(b *) {
  for i := 0; i < ; i++ {
    val := (bigInt)
    _ = val
  }
}

func BenchmarkItoaFormatIntBig(b *) {
  for i := 0; i < ; i++ {
    val := (int64(bigInt), 10)
    _ = val
  }
}

func BenchmarkItoaSprintfBig(b *) {
  for i := 0; i < ; i++ {
    val := ("%d", bigInt)
    _ = val
  }
}

There are three sets of comparisons for pressure measurement, which are less than 100, large numbers, and binary.

BenchmarkItoa-8         300000000     4.58 ns/op    0 B/op    0 allocs/op
BenchmarkItoaFormatInt-8     500000000     3.07 ns/op    0 B/op    0 allocs/op
BenchmarkItoaBase2Sprintf-8   20000000     86.4 ns/op    16 B/op    2 allocs/op
BenchmarkItoaBase2FormatInt-8  50000000     30.2 ns/op    8 B/op    1 allocs/op
BenchmarkItoaSprintf-8      20000000     83.5 ns/op    16 B/op    2 allocs/op
BenchmarkItoaBig-8        30000000     44.6 ns/op    16 B/op    1 allocs/op
BenchmarkItoaFormatIntBig-8   30000000     43.9 ns/op    16 B/op    1 allocs/op
BenchmarkItoaSprintfBig-8    20000000    108 ns/op    24 B/op    2 allocs/op
  1. Sprintf is the worst in all cases, so don't use this package.
  2. If the situation is less than 100, there will be acceleration, not only performance acceleration, because the result is calculated in advance, and there is no need to apply for memory.
  3. FormatInt decimal performance is the best, and other cases are one order of magnitude worse.
  4. Although Itoa only encapsulates FormatInt, it still has some impact on performance.

The code involved in this article can be fromheredownload.

The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.