22.5 strconv: Atoi, Itoa, ParseFloat, FormatFloat, ParseBool
Right, so you’ve printed some stuff with fmt, you’ve sliced and diced with strings, and now you need to make sense of the messy, chaotic real world where data doesn’t come neatly wrapped as a string or an int. It comes as text files, HTTP headers, user input—a veritable soup of digits and letters. This is where strconv (short for “string conversion”) becomes your best friend. It’s the unsung hero that does the gritty, unglamorous work of turning strings into numbers and booleans and back again. It’s not pretty, but it’s absolutely essential.
The Workhorses: Atoi and Itoa
Let’s start with the two you’ll use the most: Atoi (“ASCII to Integer”) and Itoa (“Integer to ASCII”).
s := "42"
i, err := strconv.Atoi(s)
if err != nil {
log.Fatal("Well, that didn't work. Probably not a number.")
}
fmt.Printf("The integer is %d and its type is %T\n", i, i) // The integer is 42 and its type is int
backToStr := strconv.Itoa(i)
fmt.Printf("The string is %q\n", backToStr) // The string is "42"
See how Atoi returns two values? That’s your first clue that this operation can fail spectacularly. What happens if the string is "fourty-two"? Or ""? Or "42 " with a trailing space? It fails, that’s what. Atoi is notoriously strict; it expects a string that represents nothing but a base-10 integer. No whitespace, no commas, no funny business. This is the most common pitfall. You must handle that err.
Itoa, on the other hand, is the optimistic, can-do function of the bunch. Since any int can be represented as a string, it can’t fail. It just does the thing. It’s a one-way ticket to String Town. Under the hood, Atoi is literally a wrapper for ParseInt(s, 10, 0), and Itoa is a wrapper for FormatInt(int64(i), 10). They’re convenient shortcuts for the most common case.
When Atoi Isn’t Enough: ParseInt and Friends
So you need to parse a hexadecimal number from a config file? Or maybe you’re dealing with base-8? Atoi throws its hands up and walks away. This is where you graduate to the more powerful ParseInt, ParseUint, and ParseFloat.
hexStr := "1a"
// ParseInt(string, base, bitSize)
i64, err := strconv.ParseInt(hexStr, 16, 64)
if err != nil { /* handle error */ }
fmt.Println(i64) // 26
// A leading '0' doesn't make it octal like in some languages. You HAVE to specify the base.
octalStr := "12"
i64, err = strconv.ParseInt(octalStr, 8, 64) // This is 10 in decimal
if err != nil { /* handle error */ }
fmt.Println(i64) // 10
The bitSize parameter (8, 16, 32, 64) is crucial. It doesn’t mean “parse this and give me a 64-bit int”; it means “parse this string and then make sure the resulting value fits into a signed/unsigned integer of the specified bit size.” If you try to parse "999" with a bitSize of 8, it will return an error because 999 is way bigger than an int8 can handle. This is your built-in overflow check.
Formatting Floats: The Precision of Chaos
If you thought integers were fussy, wait until you meet FormatFloat. This function has more parameters than a spaceship cockpit because formatting floats for output is a surprisingly complex problem. You’re not just converting; you’re deciding how to represent it.
f := 12345.6789
// FormatFloat(value, format, precision, bitSize)
// 'f' format: no exponent, plain old decimal digits.
fmt.Println(strconv.FormatFloat(f, 'f', -1, 64)) // 12345.6789
fmt.Println(strconv.FormatFloat(f, 'f', 2, 64)) // 12345.68
// 'e' format: scientific notation.
fmt.Println(strconv.FormatFloat(f, 'e', -1, 64)) // 1.23456789e+04
// 'b' format is... an eccentric choice. It outputs the float in exp2 notation. You'll almost never use it.
// It's the kind of thing you'd use if you were debugging the IEEE 754 representation itself.
fmt.Println(strconv.FormatFloat(f, 'b', -1, 64)) // 8687443681198285p-39
The precision for the 'f' and 'e' formats is the number of digits after the decimal point. Using -1 tells it to use the smallest number of digits necessary to represent the value accurately. This is usually what you want. The format byte is where you make the big stylistic choice: plain decimal ('f'), compact scientific ('e'), or the occasionally useful 'G' which switches between 'f' and 'e' automatically for large numbers.
The Deceptively Simple ParseBool
ParseBool is wonderfully, brutally literal. It doesn’t do sentiment analysis. It doesn’t check for “yes” or “no”. It recognizes:
"true"->true"false"->false"1"->true"0"->false
Any other string, regardless of case ("TRUE", "True"), any other number ("2"), any other thought you might have—results in an error. It’s the most opinionated function in the standard library, and I respect it for that. It forces you to sanitize your input first, which is always the correct approach.
b, err := strconv.ParseBool("1") // b == true, err == nil
b, err = strconv.ParseBool("t") // error
b, err = strconv.ParseBool("false") // b == false, err == nil
b, err = strconv.ParseBool("FaLsE") // error - case matters!
The takeaway? strconv is a library of sharp, precise tools, not a collection of forgiving, “guess what I meant” functions. It demands clean input and careful error handling. But that precision is its greatest strength. It means you always know exactly what you’re getting, and there are no magical, hidden behaviors to trip you up later. Use it wisely.