- Go言語
Goの文字列⇔数値変換|strconv Atoi・ParseInt早見表
Goで文字列と数値を変換するには、標準ライブラリの strconv パッケージを使います。文字列から int は strconv.Atoi、int から文字列は strconv.Itoa が基本で、型や基数を細かく決めたいときは ParseInt / FormatInt などを選びます。 string(65) のような型変換は数字の文字列にはならず、文字コード 65 の "A" になる点が最初のつまずきどころです。
この記事では、string と int / int64 / uint / float64 / bool の相互変換を「変換元 → 変換先 → 関数 → コード」の早見表にまとめ、go1.24.7 で実行した結果をそのまま載せています。 空文字・全角数字・前後の空白・桁あふれで出るエラーメッセージ、errors.Is での判別、base と bitSize の意味、fmt.Sprint との使い分けまで、コピペで使える関数と一緒に確認できます。
文字列⇔数値変換の早見表
| 変換元 | 変換先 | 関数 | コード |
|---|---|---|---|
| string | int | strconv.Atoi |
n, err := strconv.Atoi("123") |
| string | int64 | strconv.ParseInt |
n, err := strconv.ParseInt("123", 10, 64) |
| string | int32 など | strconv.ParseInt と型変換 |
n, err := strconv.ParseInt(s, 10, 32) のあと int32(n) |
| string | uint64 | strconv.ParseUint |
u, err := strconv.ParseUint("42", 10, 64) |
| string | float64 | strconv.ParseFloat |
f, err := strconv.ParseFloat("3.14", 64) |
| string | bool | strconv.ParseBool |
b, err := strconv.ParseBool("true") |
| string(16進) | int64 | strconv.ParseInt |
n, err := strconv.ParseInt("ff", 16, 64) |
| int | string | strconv.Itoa |
s := strconv.Itoa(123) |
| int64 | string | strconv.FormatInt |
s := strconv.FormatInt(n, 10) |
| uint64 | string | strconv.FormatUint |
s := strconv.FormatUint(u, 10) |
| float64 | string | strconv.FormatFloat |
s := strconv.FormatFloat(f, 'f', -1, 64) |
| float64 | string(小数2桁) | strconv.FormatFloat |
s := strconv.FormatFloat(f, 'f', 2, 64) |
| bool | string | strconv.FormatBool |
s := strconv.FormatBool(true) |
| int | string(2進・16進) | strconv.FormatInt |
s := strconv.FormatInt(255, 16) |
| なんでも | string | fmt.Sprint |
s := fmt.Sprint(v) |
| int | string(ゼロ埋め) | fmt.Sprintf |
s := fmt.Sprintf("%05d", n) |
| int | int64 / float64 | 型変換 | int64(n)、float64(n) |
| float64 | int | 型変換(小数は切り捨て) | int(f) |
| int(文字コード) | string(1文字) | 型変換 | string(rune(65)) |
文字列から数値への関数は必ず 値と error の 2 つ を返します。数値から文字列への関数は error を返しません。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Goの文字列⇔数値変換|strconv Atoi・ParseInt早見表」
検証環境
| 項目 | 内容 |
|---|---|
| Go | go1.24.7 linux/amd64 |
| 実行方法 | go run main.go(go vet の確認だけ go.mod つき) |
| 外部モジュール | なし(標準ライブラリのみ) |
| int の大きさ | 64 ビット |
int の大きさは環境で決まります。32 ビット環境では int の範囲が狭くなるため、桁あふれの境界が変わります(32 ビット環境は未実行)。
基本の変換をまとめて動かす
早見表の関数を、型と値がわかる形でまとめて実行します。
package main
import (
"fmt"
"strconv"
)
func main() {
// ---- 文字列 → 数値・真偽値 ----
i, err := strconv.Atoi("123")
fmt.Printf("Atoi %T %v %v\n", i, i, err)
i64, err := strconv.ParseInt("-9000000000", 10, 64)
fmt.Printf("ParseInt %T %v %v\n", i64, i64, err)
u64, err := strconv.ParseUint("42", 10, 64)
fmt.Printf("ParseUint %T %v %v\n", u64, u64, err)
f, err := strconv.ParseFloat("3.14", 64)
fmt.Printf("ParseFloat %T %v %v\n", f, f, err)
b, err := strconv.ParseBool("true")
fmt.Printf("ParseBool %T %v %v\n", b, b, err)
// ---- 数値・真偽値 → 文字列 ----
fmt.Printf("Itoa %q\n", strconv.Itoa(123))
fmt.Printf("FormatInt %q\n", strconv.FormatInt(-9000000000, 10))
fmt.Printf("FormatUint %q\n", strconv.FormatUint(42, 10))
fmt.Printf("FormatFloat %q\n", strconv.FormatFloat(3.14, 'f', -1, 64))
fmt.Printf("FormatBool %q\n", strconv.FormatBool(true))
// ---- 数値どうし ----
var n int = 300
fmt.Println("int→int64 ", int64(n))
fmt.Println("int→float64", float64(n)/7)
x, y := 3.99, -3.99
fmt.Println("float→int ", int(x), int(y))
fmt.Println("int→int8 ", int8(n))
fmt.Println("int→uint ", uint(n))
m := -1
fmt.Println("-1→uint8 ", uint8(m))
}
実行結果:
Atoi int 123 <nil>
ParseInt int64 -9000000000 <nil>
ParseUint uint64 42 <nil>
ParseFloat float64 3.14 <nil>
ParseBool bool true <nil>
Itoa "123"
FormatInt "-9000000000"
FormatUint "42"
FormatFloat "3.14"
FormatBool "true"
int→int64 300
int→float64 42.857142857142854
float→int 3 -3
int→int8 44
int→uint 300
-1→uint8 255
- Atoi は int、ParseInt は int64、ParseUint は uint64、ParseFloat は float64 を返します。ParseInt の第3引数に 32 を渡しても、戻り値の型は int64 のままです(後述)。
- float64 から int への型変換は、四捨五入ではなく小数部の切り捨て です。3.99 は 3、-3.99 は -3 になります。四捨五入したいときは
int(math.Round(x))を使います。 - 数値どうしの型変換は範囲を確かめません。300 を int8 にすると 44、-1 を uint8 にすると 255 になり、エラーは出ません。
- 定数をそのまま
int(3.99)と書くと、cannot convert 3.99 (untyped float constant) to type intというコンパイルエラーになります。変数に入れた値であれば変換できます。
Atoi で出るエラーを再現する(空文字・全角数字・空白・桁あふれ)
入力フォームや CSV から受け取った文字列は、数字だけとは限りません。Atoi にいろいろな文字列を渡して、戻り値とエラーメッセージを集めました。
package main
import (
"errors"
"fmt"
"strconv"
)
func main() {
inputs := []string{"", "123", " 42", "42\n", "12a", "1.5", "1,234", "+5", "-0", "0x1F", "1_000", "9223372036854775808"}
for _, s := range inputs {
n, err := strconv.Atoi(s)
kind := "OK"
switch {
case errors.Is(err, strconv.ErrSyntax):
kind = "ErrSyntax"
case errors.Is(err, strconv.ErrRange):
kind = "ErrRange"
}
fmt.Printf("%-22q 値=%-20d %-9s %v\n", s, n, kind, err)
}
}
実行結果:
"" 値=0 ErrSyntax strconv.Atoi: parsing "": invalid syntax
"123" 値=0 ErrSyntax strconv.Atoi: parsing "123": invalid syntax
" 42" 値=0 ErrSyntax strconv.Atoi: parsing " 42": invalid syntax
"42\n" 値=0 ErrSyntax strconv.Atoi: parsing "42\n": invalid syntax
"12a" 値=0 ErrSyntax strconv.Atoi: parsing "12a": invalid syntax
"1.5" 値=0 ErrSyntax strconv.Atoi: parsing "1.5": invalid syntax
"1,234" 値=0 ErrSyntax strconv.Atoi: parsing "1,234": invalid syntax
"+5" 値=5 OK <nil>
"-0" 値=0 OK <nil>
"0x1F" 値=0 ErrSyntax strconv.Atoi: parsing "0x1F": invalid syntax
"1_000" 値=0 ErrSyntax strconv.Atoi: parsing "1_000": invalid syntax
"9223372036854775808" 値=9223372036854775807 ErrRange strconv.Atoi: parsing "9223372036854775808": value out of range
| 入力 | 結果 | 種別 | 補足 |
|---|---|---|---|
| 空文字 | 0 とエラー | ErrSyntax | 未入力は 0 にはならずエラーになる |
| 全角の 123 | 0 とエラー | ErrSyntax | 半角に直してから渡す |
| 前に空白 | 0 とエラー | ErrSyntax | strings.TrimSpace で取り除く |
| 末尾に改行 | 0 とエラー | ErrSyntax | 標準入力やファイルから読んだ行で起きやすい |
| 12a、1.5、1,234 | 0 とエラー | ErrSyntax | 途中まで読んで 12 を返す、という動きはしない |
| +5、-0 | 5、0 | OK | 先頭の符号は受け付ける |
| 0x1F、1_000 | 0 とエラー | ErrSyntax | Atoi は 10 進のみ。base に 0 を渡す ParseInt なら読める |
| 9223372036854775808 | 最大値とエラー | ErrRange | int64 の最大値を 1 超えている |
エラーメッセージは「strconv.関数名: parsing "入力": 理由」の形 で、理由は invalid syntax(書式が合わない)と value out of range(範囲を超えた)の 2 種類が中心です。桁あふれのときは、0 ではなく その型の最大値(または最小値)とエラーの両方 が返ります。エラーを確かめずに値だけ使うと、上限に張り付いた値がそのまま流れていくので、必ず err を先に確認します。
errors.Is で ErrSyntax と ErrRange を判別する(コピペ用関数つき)
利用者に見せるメッセージを「数字で入力してください」と「桁数が多すぎます」に分けたいときは、errors.Is で strconv.ErrSyntax と strconv.ErrRange を見分けます。 前後の空白や全角数字を整える関数、既定値を返す関数と合わせて、コピペで使える形にまとめました。
package main
import (
"errors"
"fmt"
"strconv"
"strings"
)
// ToInt は前後の空白と全角数字を整えてから int に変換する
func ToInt(s string) (int, error) {
s = strings.TrimSpace(s)
s = strings.Map(func(r rune) rune {
switch {
case r >= '0' && r <= '9':
return r - '0' + '0'
case r == '-' || r == 'ー':
return '-'
case r == ',' || r == ',':
return -1 // 桁区切りは取り除く
}
return r
}, s)
return strconv.Atoi(s)
}
// AtoiOr は変換できないときに既定値を返す
func AtoiOr(s string, def int) int {
n, err := strconv.Atoi(s)
if err != nil {
return def
}
return n
}
// ParseQty は 1〜max の範囲の数量だけを受け付け、理由を言葉で返す
func ParseQty(s string, max int) (int, error) {
n, err := strconv.Atoi(strings.TrimSpace(s))
switch {
case errors.Is(err, strconv.ErrSyntax):
return 0, fmt.Errorf("数量 %q は数字で入力してください", s)
case errors.Is(err, strconv.ErrRange):
return 0, fmt.Errorf("数量 %q は桁数が多すぎます", s)
case err != nil:
return 0, err
case n < 1 || n > max:
return 0, fmt.Errorf("数量は 1〜%d で入力してください(入力: %d)", max, n)
}
return n, nil
}
// ToInt32 は範囲を確かめてから int32 にする
func ToInt32(s string) (int32, error) {
n, err := strconv.ParseInt(s, 10, 32)
return int32(n), err
}
func main() {
for _, s := range []string{" 42 ", "123", "1,234", "-5", "12a", "\t7\n"} {
n, err := ToInt(s)
fmt.Printf("ToInt(%q) = %d, %v\n", s, n, err)
}
n, err := ToInt(" 8 ") // 前後は全角空白
fmt.Println("全角空白つき:", n, err)
fmt.Println(AtoiOr("15", 10), AtoiOr("", 10), AtoiOr("abc", 10))
for _, s := range []string{"3", " 3 ", "abc", "", "0", "1000", "99999999999999999999"} {
n, err := ParseQty(s, 99)
fmt.Printf("ParseQty(%q) = %d, %v\n", s, n, err)
}
fmt.Println(ToInt32("2147483647"))
fmt.Println(ToInt32("2147483648"))
// 範囲を確かめない変換は値が変わる
big, _ := strconv.ParseInt("2147483648", 10, 64)
fmt.Println("int32(2147483648) =", int32(big))
}
実行結果:
ToInt(" 42 ") = 42, <nil>
ToInt("123") = 123, <nil>
ToInt("1,234") = 1234, <nil>
ToInt("-5") = -5, <nil>
ToInt("12a") = 0, strconv.Atoi: parsing "12a": invalid syntax
ToInt("\t7\n") = 7, <nil>
全角空白つき: 8 <nil>
15 10 10
ParseQty("3") = 3, <nil>
ParseQty(" 3 ") = 3, <nil>
ParseQty("abc") = 0, 数量 "abc" は数字で入力してください
ParseQty("") = 0, 数量 "" は数字で入力してください
ParseQty("0") = 0, 数量は 1〜99 で入力してください(入力: 0)
ParseQty("1000") = 0, 数量は 1〜99 で入力してください(入力: 1000)
ParseQty("99999999999999999999") = 0, 数量 "99999999999999999999" は桁数が多すぎます
2147483647 <nil>
2147483647 strconv.ParseInt: parsing "2147483648": value out of range
int32(2147483648) = -2147483648
| 関数 | 役割 | 使いどころ |
|---|---|---|
ToInt |
空白・全角数字・桁区切りを整えてから変換 | 人が手で入力した値 |
AtoiOr |
変換できなければ既定値を返す | 省略できる設定値やクエリパラメータ |
ParseQty |
エラーの種類ごとに日本語のメッセージを返す | 入力チェック |
ToInt32 |
bitSize で範囲を確かめてから int32 にする | 列の型が 32 ビットの値 |
strings.TrimSpaceは半角空白・タブ・改行に加えて、全角空白(U+3000)も取り除きます。出力の「全角空白つき: 8」がその結果です。- 全角数字や桁区切りをどこまで受け付けるかは、画面の仕様として決めておく部分です。受け付けない方針なら、TrimSpace だけにして ErrSyntax のメッセージを返します。
- err == strconv.ErrSyntax という比較は false になります。 strconv が返すのは、ErrSyntax を包んだ
*strconv.NumErrorだからです。比較には errors.Is を使います(次の章の出力の最終行で確認しています)。
エラーの中身を取り出したいときは errors.As を使います。NumError には Func(関数名)、Num(入力)、Err(理由)の 3 つのフィールドがあります。
_, err := strconv.ParseInt("12a", 10, 64)
var ne *strconv.NumError
if errors.As(err, &ne) {
fmt.Printf("Func=%q Num=%q Err=%v\n", ne.Func, ne.Num, ne.Err)
}
base と bitSize の意味を出力で確かめる
ParseInt と ParseUint は (s, base, bitSize)、ParseFloat は (s, bitSize) を引数に取ります。
| 引数 | 意味 | 指定できる値 |
|---|---|---|
| base | 何進数として読むか | 2〜36、または 0(接頭辞から自動で判定) |
| bitSize(整数) | 結果が収まるべき型の大きさ | 0(int)、8、16、32、64 |
| bitSize(小数) | float32 と float64 のどちらの精度で読むか | 32、64 |
bitSize は「戻り値の型」ではなく「範囲チェックの幅」 です。戻り値は常に int64(ParseUint は uint64、ParseFloat は float64)なので、小さい型で使うときは自分で型変換します。strconv の公式ドキュメントにも、Atoi は ParseInt(s, 10, 0) を int に変換したもの、Itoa は FormatInt(int64(i), 10) と同じと書かれています。
package main
import (
"errors"
"fmt"
"strconv"
)
func show(label string, v any, err error) {
fmt.Printf("%-34s 値=%v err=%v\n", label, v, err)
}
func main() {
v1, err := strconv.ParseInt("127", 10, 8)
show(`ParseInt("127", 10, 8)`, v1, err)
v1, err = strconv.ParseInt("128", 10, 8)
show(`ParseInt("128", 10, 8)`, v1, err)
v1, err = strconv.ParseInt("3000000000", 10, 32)
show(`ParseInt("3000000000", 10, 32)`, v1, err)
v1, err = strconv.ParseInt("3000000000", 10, 0)
show(`ParseInt("3000000000", 10, 0)`, v1, err)
v2, err := strconv.ParseUint("-1", 10, 64)
show(`ParseUint("-1", 10, 64)`, v2, err)
v2, err = strconv.ParseUint("256", 10, 8)
show(`ParseUint("256", 10, 8)`, v2, err)
fmt.Println("---- base ----")
v1, err = strconv.ParseInt("ff", 16, 64)
show(`ParseInt("ff", 16, 64)`, v1, err)
v1, err = strconv.ParseInt("0xff", 16, 64)
show(`ParseInt("0xff", 16, 64)`, v1, err)
v1, err = strconv.ParseInt("0xff", 0, 64)
show(`ParseInt("0xff", 0, 64)`, v1, err)
v1, err = strconv.ParseInt("0b1010", 0, 64)
show(`ParseInt("0b1010", 0, 64)`, v1, err)
v1, err = strconv.ParseInt("010", 0, 64)
show(`ParseInt("010", 0, 64)`, v1, err)
v1, err = strconv.ParseInt("010", 10, 64)
show(`ParseInt("010", 10, 64)`, v1, err)
v1, err = strconv.ParseInt("1_000", 0, 64)
show(`ParseInt("1_000", 0, 64)`, v1, err)
v1, err = strconv.ParseInt("1010", 2, 64)
show(`ParseInt("1010", 2, 64)`, v1, err)
v1, err = strconv.ParseInt("z", 36, 64)
show(`ParseInt("z", 36, 64)`, v1, err)
v1, err = strconv.ParseInt("10", 37, 64)
show(`ParseInt("10", 37, 64)`, v1, err)
v1, err = strconv.ParseInt("10", 10, 65)
show(`ParseInt("10", 10, 65)`, v1, err)
fmt.Println("---- FormatInt ----")
for _, base := range []int{2, 8, 10, 16, 36} {
fmt.Printf("FormatInt(255, %2d) = %q\n", base, strconv.FormatInt(255, base))
}
fmt.Printf("FormatInt(-255, 16) = %q\n", strconv.FormatInt(-255, 16))
fmt.Println("---- float ----")
f, err := strconv.ParseFloat("1e400", 64)
show(`ParseFloat("1e400", 64)`, f, err)
f, err = strconv.ParseFloat("abc", 64)
show(`ParseFloat("abc", 64)`, f, err)
f, err = strconv.ParseFloat("1,234.5", 64)
show(`ParseFloat("1,234.5", 64)`, f, err)
f, err = strconv.ParseFloat("NaN", 64)
show(`ParseFloat("NaN", 64)`, f, err)
f, err = strconv.ParseFloat("-Inf", 64)
show(`ParseFloat("-Inf", 64)`, f, err)
f, err = strconv.ParseFloat("1e3", 64)
show(`ParseFloat("1e3", 64)`, f, err)
f, err = strconv.ParseFloat("0.1", 32)
show(`ParseFloat("0.1", 32)`, f, err)
fmt.Println("float32(f) =", float32(f))
f, err = strconv.ParseFloat("3.5e38", 32)
show(`ParseFloat("3.5e38", 32)`, f, err)
fmt.Println("---- bool ----")
for _, s := range []string{"1", "t", "TRUE", "True", "0", "false", "yes", "on", "tRUE", ""} {
b, err := strconv.ParseBool(s)
fmt.Printf("ParseBool(%q) = %v, %v\n", s, b, err)
}
fmt.Println("---- NumError ----")
_, err = strconv.ParseInt("12a", 10, 64)
var ne *strconv.NumError
if errors.As(err, &ne) {
fmt.Printf("Func=%q Num=%q Err=%v\n", ne.Func, ne.Num, ne.Err)
}
fmt.Println(err == strconv.ErrSyntax, errors.Is(err, strconv.ErrSyntax))
}
実行結果:
ParseInt("127", 10, 8) 値=127 err=<nil>
ParseInt("128", 10, 8) 値=127 err=strconv.ParseInt: parsing "128": value out of range
ParseInt("3000000000", 10, 32) 値=2147483647 err=strconv.ParseInt: parsing "3000000000": value out of range
ParseInt("3000000000", 10, 0) 値=3000000000 err=<nil>
ParseUint("-1", 10, 64) 値=0 err=strconv.ParseUint: parsing "-1": invalid syntax
ParseUint("256", 10, 8) 値=255 err=strconv.ParseUint: parsing "256": value out of range
---- base ----
ParseInt("ff", 16, 64) 値=255 err=<nil>
ParseInt("0xff", 16, 64) 値=0 err=strconv.ParseInt: parsing "0xff": invalid syntax
ParseInt("0xff", 0, 64) 値=255 err=<nil>
ParseInt("0b1010", 0, 64) 値=10 err=<nil>
ParseInt("010", 0, 64) 値=8 err=<nil>
ParseInt("010", 10, 64) 値=10 err=<nil>
ParseInt("1_000", 0, 64) 値=1000 err=<nil>
ParseInt("1010", 2, 64) 値=10 err=<nil>
ParseInt("z", 36, 64) 値=35 err=<nil>
ParseInt("10", 37, 64) 値=0 err=strconv.ParseInt: parsing "10": invalid base 37
ParseInt("10", 10, 65) 値=0 err=strconv.ParseInt: parsing "10": invalid bit size 65
---- FormatInt ----
FormatInt(255, 2) = "11111111"
FormatInt(255, 8) = "377"
FormatInt(255, 10) = "255"
FormatInt(255, 16) = "ff"
FormatInt(255, 36) = "73"
FormatInt(-255, 16) = "-ff"
---- float ----
ParseFloat("1e400", 64) 値=+Inf err=strconv.ParseFloat: parsing "1e400": value out of range
ParseFloat("abc", 64) 値=0 err=strconv.ParseFloat: parsing "abc": invalid syntax
ParseFloat("1,234.5", 64) 値=0 err=strconv.ParseFloat: parsing "1,234.5": invalid syntax
ParseFloat("NaN", 64) 値=NaN err=<nil>
ParseFloat("-Inf", 64) 値=-Inf err=<nil>
ParseFloat("1e3", 64) 値=1000 err=<nil>
ParseFloat("0.1", 32) 値=0.10000000149011612 err=<nil>
float32(f) = 0.1
ParseFloat("3.5e38", 32) 値=+Inf err=strconv.ParseFloat: parsing "3.5e38": value out of range
---- bool ----
ParseBool("1") = true, <nil>
ParseBool("t") = true, <nil>
ParseBool("TRUE") = true, <nil>
ParseBool("True") = true, <nil>
ParseBool("0") = false, <nil>
ParseBool("false") = false, <nil>
ParseBool("yes") = false, strconv.ParseBool: parsing "yes": invalid syntax
ParseBool("on") = false, strconv.ParseBool: parsing "on": invalid syntax
ParseBool("tRUE") = false, strconv.ParseBool: parsing "tRUE": invalid syntax
ParseBool("") = false, strconv.ParseBool: parsing "": invalid syntax
---- NumError ----
Func="ParseInt" Num="12a" Err=invalid syntax
false true
出力から読み取れること
| 確認した内容 | 結果 |
|---|---|
| bitSize 8 で 128 を読む | 127(int8 の最大値)と value out of range |
| bitSize 32 で 30 億を読む | 2147483647 と value out of range |
| bitSize 0 で 30 億を読む | そのまま読める(この環境の int は 64 ビット) |
| ParseUint にマイナスの値 | invalid syntax(範囲ではなく書式のエラー) |
| base 16 で "0xff" | invalid syntax(base を明示したら接頭辞は付けない) |
| base 0 で "0xff"、"0b1010"、"1_000" | 255、10、1000(Go のリテラルと同じ書き方が通る) |
| base 0 で "010" | 8(先頭の 0 は 8 進数として読まれる) |
| base 10 で "010" | 10 |
| base 37、bitSize 65 | invalid base 37、invalid bit size 65 |
| ParseFloat で "1e400" | +Inf と value out of range |
| ParseFloat で "NaN"、"-Inf" | エラーにならず NaN、-Inf として読める |
| ParseFloat("0.1", 32) | 戻り値は float64 の 0.10000000149011612。float32 に変換すると 0.1 |
| ParseBool が受け付ける値 | 1、t、T、TRUE、true、True、0、f、F、FALSE、false、False(出力ではこのうち 6 つを確認) |
| ParseBool で "yes"、"on"、"tRUE" | invalid syntax |
- 利用者が入力した数値を読むときは base 10 を明示する のが安全です。base 0 だと「010」が 8 になります。設定ファイルで 16 進や 2 進も書けるようにしたい、という場面では base 0 が便利です。
- ParseFloat は "NaN" や "Inf" を数値として受け付けます。受け付けたくない場合は、変換後に
math.IsNaN(f)とmath.IsInf(f, 0)で確かめます。 - "yes" や "on" を真として扱いたいときは、ParseBool の前に自分で対応表を持ちます。
- FormatInt の base も 2〜36 で、16 進は小文字(ff)で返ります。大文字にしたいときは
strings.ToUpperを通すかfmt.Sprintf("%X", n)を使います。
string(65) が "A" になる罠と go vet の警告
他の言語の感覚で string(n) と書くと、数字の文字列ではなく その番号の文字(Unicode コードポイント)1 文字 になります。
package main
import (
"fmt"
"strconv"
)
func main() {
n := 65
s := string(n)
fmt.Printf("string(65) = %q\n", s)
fmt.Printf("string(rune(65)) = %q\n", string(rune(n)))
fmt.Printf("strconv.Itoa(65) = %q\n", strconv.Itoa(n))
fmt.Printf("string(12354) = %q\n", string(rune(12354)))
fmt.Printf("string(-1) = %q\n", string(rune(-1)))
fmt.Printf("fmt.Sprint(65) = %q\n", fmt.Sprint(n))
}
実行結果:
string(65) = "A"
string(rune(65)) = "A"
strconv.Itoa(65) = "65"
string(12354) = "あ"
string(-1) = "�"
fmt.Sprint(65) = "65"
このコードは go build も go run も通ります。一方で go vet を実行すると、次のように教えてくれます。
# strint
# [strint]
./main.go:10:7: conversion from int to string yields a string of one rune, not a string of digits
「int から string への変換は 1 文字の文字列になり、数字の並びにはなりません」 という意味です。同じフォルダにテストファイルを置いて go test を実行したときも、同じメッセージが出てビルドが止まることを確認しました。go test は実行前に vet の一部を自動でかけるためです。
| やりたいこと | 書き方 | 結果 |
|---|---|---|
| 65 を "65" にしたい | strconv.Itoa(65) |
"65" |
| 文字コード 65 を文字にしたい | string(rune(65)) |
"A" |
| 意図があいまいな書き方 | string(65) |
"A"(go vet が知らせる) |
文字コードから文字を作りたいときは、string(rune(n)) と rune を経由して書けば、意図が伝わり go vet も通ります。範囲外の値(-1 など)は置換文字(U+FFFD)になります。
fmt.Sprint・Sprintf と strconv の使い分け
数値を文字列にする方法は、strconv のほかに fmt.Sprint と fmt.Sprintf があります。出力と速度の両方を比べました。
package main
import (
"fmt"
"strconv"
"testing"
)
func main() {
// ---- fmt と strconv の出力の違い ----
fmt.Printf("%q %q\n", fmt.Sprint(42), strconv.Itoa(42))
fmt.Printf("%q %q\n", fmt.Sprint(3.0), strconv.FormatFloat(3.0, 'f', -1, 64))
fmt.Printf("%q %q\n", fmt.Sprint(1234567.0), strconv.FormatFloat(1234567.0, 'f', -1, 64))
fmt.Printf("%q %q\n", fmt.Sprint(123456789012345678901.0), strconv.FormatFloat(123456789012345678901.0, 'f', -1, 64))
fmt.Printf("%q %q\n", fmt.Sprint(0.000001234), strconv.FormatFloat(0.000001234, 'f', -1, 64))
fmt.Printf("%q\n", fmt.Sprintf("%05d|%5d|%-5d|%x|%08.3f", 42, 42, 42, 255, 3.14159))
fmt.Println("---- FormatFloat ----")
x := 1234.5678
fmt.Println(strconv.FormatFloat(x, 'f', 2, 64))
fmt.Println(strconv.FormatFloat(x, 'f', 0, 64))
fmt.Println(strconv.FormatFloat(x, 'f', -1, 64))
fmt.Println(strconv.FormatFloat(x, 'e', 3, 64))
fmt.Println(strconv.FormatFloat(x, 'g', 3, 64))
fmt.Println(strconv.FormatFloat(x, 'g', -1, 64))
fmt.Println(strconv.FormatFloat(0.1+0.2, 'f', -1, 64))
fmt.Println(strconv.FormatFloat(0.1+0.2, 'f', 2, 64))
fmt.Println(strconv.FormatFloat(2.5, 'f', 0, 64), strconv.FormatFloat(3.5, 'f', 0, 64), strconv.FormatFloat(2.675, 'f', 2, 64))
fmt.Println(strconv.FormatFloat(float64(float32(0.1)), 'f', -1, 64), strconv.FormatFloat(float64(float32(0.1)), 'f', -1, 32))
fmt.Println("---- Quote / Append ----")
fmt.Println(strconv.Quote("a\tb\"c"))
u, err := strconv.Unquote(`"a\tb"`)
fmt.Printf("%q %v\n", u, err)
buf := []byte("id=")
buf = strconv.AppendInt(buf, 42, 10)
buf = append(buf, " ok="...)
buf = strconv.AppendBool(buf, true)
fmt.Println(string(buf))
fmt.Println("---- 速度の目安 ----")
bench := func(name string, f func()) {
r := testing.Benchmark(func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
f()
}
})
fmt.Printf("%-22s %6.1f ns/op %3d B/op %d allocs/op\n", name, float64(r.T.Nanoseconds())/float64(r.N), r.AllocedBytesPerOp(), r.AllocsPerOp())
}
var sink string
var isink int
n := 1234567
bench("strconv.Itoa", func() { sink = strconv.Itoa(n) })
bench("strconv.FormatInt", func() { sink = strconv.FormatInt(int64(n), 10) })
bench("fmt.Sprint", func() { sink = fmt.Sprint(n) })
bench("fmt.Sprintf(%d)", func() { sink = fmt.Sprintf("%d", n) })
bench("strconv.Atoi", func() { isink, _ = strconv.Atoi("1234567") })
bench("fmt.Sscan", func() { fmt.Sscan("1234567", &isink) })
_, _ = sink, isink
}
実行結果(速度は 2 vCPU・Intel Xeon 2.10GHz のコンテナで測った目安で、環境によって変わります):
"42" "42"
"3" "3"
"1.234567e+06" "1234567"
"1.2345678901234568e+20" "123456789012345680000"
"1.234e-06" "0.000001234"
"00042| 42|42 |ff|0003.142"
---- FormatFloat ----
1234.57
1235
1234.5678
1.235e+03
1.23e+03
1234.5678
0.3
0.30
2 4 2.67
0.10000000149011612 0.1
---- Quote / Append ----
"a\tb\"c"
"a\tb" <nil>
id=42 ok=true
---- 速度の目安 ----
strconv.Itoa 38.2 ns/op 8 B/op 1 allocs/op
strconv.FormatInt 33.5 ns/op 8 B/op 1 allocs/op
fmt.Sprint 105.6 ns/op 16 B/op 2 allocs/op
fmt.Sprintf(%d) 116.9 ns/op 16 B/op 2 allocs/op
strconv.Atoi 10.3 ns/op 0 B/op 0 allocs/op
fmt.Sscan 583.6 ns/op 72 B/op 3 allocs/op
出力の違い
- int は fmt.Sprint でも strconv.Itoa でも同じ "42" です。
- float64 は違いが出ます。 fmt.Sprint(1234567.0) は "1.234567e+06" と指数表記になり、FormatFloat(x, 'f', -1, 64) は "1234567" になります。画面や CSV に出す数値は、FormatFloat の 'f' か Sprintf の %f / %.2f を使うと指数表記になりません。
- ゼロ埋めや桁そろえは Sprintf の担当です(%05d、%5d、%-5d、%x、%08.3f)。
FormatFloat の引数
strconv.FormatFloat(f, fmt, prec, bitSize) の fmt と prec の組み合わせは、次のとおりです(x = 1234.5678 の出力)。
| fmt | prec | 出力 | 意味 |
|---|---|---|---|
'f' |
2 | 1234.57 | 小数 2 桁に丸める |
'f' |
0 | 1235 | 整数に丸める |
'f' |
-1 | 1234.5678 | 元の値に戻せる最短の桁数 |
'e' |
3 | 1.235e+03 | 指数表記 |
'g' |
3 | 1.23e+03 | 有効 3 桁(大きさで表記が切り替わる) |
'g' |
-1 | 1234.5678 | 最短の表記 |
- 0.1 + 0.2 は 'f' と -1 の組み合わせで 0.30000000000000004 になります。小数の桁数を決めて表示する場合は prec に桁数を渡します('f', 2 で 0.30)。
- 丸めは 2 進数で持っている実際の値に対して行われます。2.5 は 2、3.5 は 4、2.675 は 2.67 になりました。ちょうど中間の値は偶数側に寄り、2.675 は内部ではわずかに小さい値として持たれているためです。厳密な丸めが必要な数量は、整数(最小単位)で持つ設計にすると扱いやすくなります。
- float32 由来の値を表示するときは bitSize に 32 を渡すと、0.10000000149011612 ではなく 0.1 と表示されます。
速度の目安
| 書き方 | 1 回あたり | メモリ確保 |
|---|---|---|
strconv.Itoa(n) |
約 32 ns | 1 回 |
strconv.FormatInt(int64(n), 10) |
約 35 ns | 1 回 |
fmt.Sprint(n) |
約 109 ns | 2 回 |
fmt.Sprintf("%d", n) |
約 130 ns | 2 回 |
strconv.Atoi(s) |
約 17 ns | 0 回 |
fmt.Sscan(s, &n) |
約 842 ns | 3 回 |
1 回ずつの差はごく小さいので、通常の処理ではどちらを使っても体感は変わりません。大量の行を変換するループでは strconv が向いています。バイト列に直接書き足す strconv.AppendInt / AppendBool を使うと、途中の文字列を作らずに済みます(出力の id=42 ok=true)。
どれを選ぶかの判断表
| 状況 | 選ぶもの | 理由 |
|---|---|---|
| int を文字列にするだけ | strconv.Itoa |
短く、型がはっきりする |
| int64 / uint64 を文字列に | strconv.FormatInt / FormatUint |
型変換なしで渡せる |
| 小数の桁数を決めて出す | strconv.FormatFloat か fmt.Sprintf("%.2f") |
指数表記にならない |
| 文章の中に数値を埋め込む | fmt.Sprintf |
書式をまとめて書ける |
| ゼロ埋め・桁そろえ | fmt.Sprintf |
%05d などが使える |
| 型がわからない値(any) | fmt.Sprint |
どの型でも受け取れる |
| 文字列を int にする | strconv.Atoi |
10 進の int ならこれだけで足りる |
| 範囲を型に合わせて確かめたい | strconv.ParseInt(s, 10, 32) など |
bitSize で範囲を確かめられる |
| 16 進・2 進を読む | strconv.ParseInt(s, 16, 64) |
base を指定できる |
変換コードのチェックリスト
| 確認すること | 見るポイント |
|---|---|
| err を確かめているか | 値より先に if err != nil を書いているか |
| 入力を整えているか | TrimSpace、全角数字、桁区切りの扱いを決めているか |
| エラーの見分け方 | == ではなく errors.Is を使っているか |
| 型の範囲 | int32 などに入れる前に bitSize で確かめているか |
| base | 人が入力する値は 10 を明示しているか |
string(n) |
数字にしたいなら Itoa、文字にしたいなら string(rune(n)) になっているか |
| 小数の表示 | 指数表記にならない書式('f')を選んでいるか |
| go vet | CI や保存時に go vet を通しているか |
よくある質問
Q. Atoi と ParseInt はどう使い分けますか? A. 10 進の文字列を int にするだけなら Atoi で足ります。int64 で受けたい、16 進を読みたい、int32 の範囲に収まるかを確かめたい、という場合は ParseInt を使います。公式ドキュメントでは、Atoi は ParseInt(s, 10, 0) を int に変換したものと説明されています。
Q. 空文字を 0 として扱いたいときはどうしますか?
A. Atoi("") はエラーになるので、先に if s == "" で分けるか、この記事の AtoiOr のように既定値を返す関数を用意します。「未入力」と「0 と入力した」を区別したい場面では、エラーのまま扱うほうが意図を保てます。
Q. "1,234" や "123" を読むにはどうしますか?
A. strconv はどちらも ErrSyntax を返します。strings.Map で桁区切りを取り除き、全角数字を半角に置き換えてから Atoi に渡します(この記事の ToInt)。
Q. string(n) はコンパイルエラーにならないのですか?
A. なりません。go build は通り、go vet と go test のときに「conversion from int to string yields a string of one rune, not a string of digits」と表示されます。数字にしたいなら strconv.Itoa、文字にしたいなら string(rune(n)) と書きます。
まとめ
- 文字列から数値は Atoi / ParseInt / ParseUint / ParseFloat / ParseBool、数値から文字列は Itoa / FormatInt / FormatFloat / FormatBool が基本です。
- 空文字・全角数字・前後の空白・小数点・桁区切りはすべて ErrSyntax、範囲を超えると ErrRange になり、後者は上限の値とエラーが一緒に返ります。
- エラーの判別は errors.Is(err, strconv.ErrSyntax) / errors.Is(err, strconv.ErrRange)。
==では見分けられません。 - bitSize は戻り値の型ではなく範囲チェックの幅。 戻り値は int64 / uint64 / float64 のままなので、使う型に変換します。
- base 0 は 0x・0b・0o・アンダースコアを読める 一方で、"010" が 8 になります。人が入力する値は base 10 を明示します。
string(65)は "A"。 数字にするなら strconv.Itoa、go vet を通すと気づけます。- 表示用の小数は FormatFloat の 'f' か Sprintf の %.2f、文章への埋め込みやゼロ埋めは Sprintf が向いています。
参考・出典
- strconv パッケージ(Go 公式ドキュメント): https://pkg.go.dev/strconv
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Goの文字列⇔数値変換|strconv Atoi・ParseInt早見表」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。