【備忘】Go 言語のデータ型と型変換

備忘として、Go 言語の「データ型」と「型変換（キャスト）」についてまとめておきます。

# １．Go 言語データ型一覧

|データ型|サイズ|説明|値の範囲|
|---|---|---|---|
|uint8|1byte|符号無し整数|0 ～ 255|
|uint16|2byte|符号無し整数|0 ～ 65535|
|uint32|4byte|符号無し整数|0 ～ 4294967295|
|uint64|8byte|符号無し整数|0 ～ 18446744073709551615|
|int8|1byte|符号付き整数|-128 ～ 127|
|int16|2byte|符号付き整数|-32768 ～ 32767|
|int32|4byte|符号付き整数|-2147483648 ～ 2147483647|
|int64|8byte|符号付き整数|-9223372036854775808 ～ 9223372036854775807|
|float32|4byte|単精度浮動小数点数|有効桁数6桁の実数|
|float64|8byte|倍精度浮動小数点数|有効桁数14桁の実数|
|complex64|8byte|複素数|float32の実数部と虚数部を持つ複素数|
|complex128|16byte|複素数|float64の実数部と虚数部を持つ複素数|
|uint|4byte<br>8byte|符号無し整数|uint32 または uint64 と同じ|
|int|4byte<br>8byte|符号付き整数|int32 または int64 と同じ|
|uintptr|(8byte)|ポインタ型|PC環境により異なる|
|byte|1byte|uint8のエイリアス|0 ～ 255|
|rune|4byte|int32のエイリアス|Unicodeのコードポイント1文字分を格納|
|string|-|文字列型||
|bool|1byte|論理値型|true / false|

＜出典＞
[The Go Programming Language Specification](https://golang.org/ref/spec)

**（サイズについての補足）**
データ型に bit 数が表示されているもについては、サイズは自明です。
それ以外のデータ型のサイズを、手元の環境（Windows **64bit**）で確認した結果は次のとおりでした。

```main.go
package main

import (
	"fmt"
	"unsafe"
)

func main() {
	var ui uint
	fmt.Printf("uint=%dbyte\n", unsafe.Sizeof(ui))

	var i int
	fmt.Printf("int=%dbyte\n", unsafe.Sizeof(i))

	var p uintptr
	fmt.Printf("uintptr=%dbyte\n", unsafe.Sizeof(p))

	var r rune
	fmt.Printf("rune=%dbyte\n", unsafe.Sizeof(r))

	var b bool
	fmt.Printf("bool=%dbyte\n", unsafe.Sizeof(b))
}
```

```:ターミナル（コマンドプロンプト）
C:\golang\sample>go build main.go
C:\golang\sample>.\main
uint=8byte
int=8byte
uintptr=8byte
rune=4byte
bool=1byte
```

今は、ほとんどの PC が 64bit なので、大体同じ結果になるものと思います。

# ２．Go 言語のキャスト

キャストも簡単です。
int32 にキャストする場合は `int32(キャストする変数)`
float32 にキャストする場合は `float32(キャストする変数)`
というルールで書きます。

実際にコードで確認すると次のような感じです。

```main.go
package main

import "fmt"

func main() {
	// int64 → int32
	var num64 int64 = 2147483647
	var num32 int32 = int32(num64)
	fmt.Printf("%T, %d\n", num32, num32) // int32, 2147483647

	// uint16 → uint64
	var unum16 uint16 = 65535
	var unum64 uint64 = uint64(unum16)
	fmt.Printf("%T, %d\n", unum64, unum64) // uint64, 65535

	// float64 → float32
	var f64 float64 = 123456.789876
	var f32 float32 = float32(f64)
	fmt.Printf("%T, %f\n", f32, f32) // float32, 123456.789062
}
```

```:ターミナル（コマンドプロンプト）
C:\golang\sample>go build main.go
C:\golang\sample>.\main
int32, 2147483647
uint64, 65535
float32, 123456.789062
```

float64 から float32 にキャストすると、123456.789<font color = "blue">876</font> という数値が 123456.789<font color = "red">062</font> というように、精度が落ちていることが確認できます。

### 2-1. 浮動小数点数から整数へのキャスト

浮動小数点数から整数型にキャストすると、小数点以下は切捨てとなるようです（以下のとおり）。

```main.go
package main

import "fmt"

func main() {
	// float32 → int32
	var d32 float32 = 123.789
	var num32 int32 = int32(d32)
	fmt.Printf("%T, %d\n", num32, num32) // int32, 123
}
```

```:ターミナル（コマンドプロンプト）
C:\golang\sample>go build main.go
C:\golang\sample>.\main
int32, 123
```

### 2-2. 符号あり変数と符号無し変数間のキャスト

`uint8`（符号無し整数）の値の範囲は 0 ～ 255 です。
`int8`（符号付き整数）の値の範囲は -128 ～ 127 です。
範囲の重複する 0 ～ 127 であれば、問題なくキャストできます。

範囲が重複しない `255` という数値が変数に格納されている場合に、キャストをすると次のようになります。

```main.go
package main

import "fmt"

func main() {
	// uint8 → int8
	var unum8 uint8 = 255
	var num8 int8 = int8(unum8)
	fmt.Printf("%T, %d\n", num8, num8) // int8, -1
}
```

```:ターミナル（コマンドプロンプト）
C:\golang\sample>go build main.go
C:\golang\sample>.\main
int8, -1
```

`uint8` 型の変数に入れている `255` という数値は、本来 `int8` ではオーバーフローになるはずですが、エラーは起きず `-1` という値で出力されています。
`255` と `-1` は値は見た目は違いますが、２進数にするとどちらも `11111111` を表しているため値自体は同一ということになります。

細かい説明は省略しますが、コンピュータで負の数を表す場合には、以下のように、その数値（２進数）に対応する**[２の補数](https://ja.wikipedia.org/wiki/2%E3%81%AE%E8%A3%9C%E6%95%B0)**が使用されるため、`-1` は２進数で `11111111` と表現されるためです。

```
＜-1 を二進数で表示する＞
0000 0001  :  二進数で[1]を表示する
1111 1110  :  0000 0001 の１の補数
1111 1111  :  0000 0001 の２の補数（これが[-1]を表す）

＜-5 を二進数で表示する＞
0000 0101  :  二進数で[5]を表示する
1111 1010  :  0000 0101 の１の補数
1111 1011  :  0000 0101 の２の補数（これが[-5]を表す）
```

### 2-3. オーバーフローする場合のキャスト

元のサイズよりも小さいサイズのデータ型にキャストする場合には、オーバーフローとなる場合があります。
しかし、この場合もエラーは生じません。

```main.go
package main

import "fmt"

func main() {
	// uint16 → uint8
	var unum16 uint16 = 258
	var unum8 uint8 = uint8(unum16)
	fmt.Printf("%T, %d\n", unum8, unum8) // uint8, 2
}
```

```:ターミナル（コマンドプロンプト）
C:\golang\sample>go build main.go
C:\golang\sample>.\main
uint8, 2
```

`uint16` では `258` であった変数を、`uint8` にキャストすると `2` となります。
これは、キャストの結果により、下８桁分だけの値が取得されているためです。

```
＜uint16に格納された値＞
0000 0001 0000 0010  :  二進数で[258]を表示

＜uint8に格納された値＞
          0000 0010  :  uint8 へのキャストにより下８桁（8bit分）のみ取得される
                        結果として[2]が表示される。
```

# ３．文字列と数値の相互変換

「文字列から数値に変換する場合」及び「数値から文字列に変換する場合」は、`strconv` ライブラリを使用します。

### 3-1. 文字列から数値への変換

#### 3-1-1. 一般的な変換

文字列から数値への変換は、`Atoi` 関数を使用します（atoi とは「ASCII to Integer」の意です）。
コードを書くと、次のようになります。

```main.go
package main

import (
	"fmt"
	"strconv"
)

func main() {
	var numStr1 string = "2468"
	i, err := strconv.Atoi(numStr1)
	fmt.Println(i)   // 2468
	fmt.Println(err) // <nil>

	var numStr2 string = "2468yen" // 数字以外の文字が混じっている場合
	j, err := strconv.Atoi(numStr2)
	fmt.Println(j)   // 0
	fmt.Println(err) // strconv.Atoi: parsing "2468yen": invalid syntax
}
```

```:ターミナル（コマンドプロンプト）
C:\golang\sample>go build main.go
C:\golang\sample>.\main
2468
<nil>
0
strconv.Atoi: parsing "2468yen": invalid syntax
```

Atoi 関数を使用すると２つの値が戻ってきます。
１つ目は、int 型で文字列を変換した数値が返されます。
２つ目は、エラーの内容が返されます。

数値以外の文字を変換しようとすると、エラーが生じます。
上記のコードで、`"2468yen"` という文字列を変換しようとすると、`strconv.Atoi: parsing "2468yen": invalid syntax` という構文エラーが表示され、数値としては `0` という値が返されています。

エラーの取得が不要な場合は、次のように `err` を `_` に置き換えます。

```main.go
package main

import (
	"fmt"
	"strconv"
)

func main() {
	var numStr string = "12345678"
	var n int // あらかじめ変数を指定する場合はint型
	n, _ = strconv.Atoi(numStr)
	fmt.Println(n) // 12345678
}
```

なお、数値は int 型で返されますので、あらかじめ変数を指定する場合は int 型の変数としておきます。

#### 3-1-2. 詳細な変換指定

```main.go
package main

import (
	"fmt"
	"strconv"
)

func main() {
	b, _ := strconv.ParseBool("true")
	fmt.Printf("%T, %t\n", b, b) // bool, true

	f, _ := strconv.ParseFloat("3.1415", 64)
	fmt.Printf("%T, %f\n", f, f) // float64, 3.141500

	i, _ := strconv.ParseInt("-42", 10, 32)
	fmt.Printf("%T, %d\n", i, i) // int64, -42

	u, _ := strconv.ParseUint("42", 10, 16)
	fmt.Printf("%T, %d\n", u, u) // uint64, 42
}
```

```:ターミナル（コマンドプロンプト）
C:\golang\sample>go build main.go
C:\golang\sample>.\main
bool, true
float64, 3.141500
int64, -42
uint64, 42
```

**bool 型**への変換には `ParseBool` 関数を使用します。

**float 型**への変換には `ParseFloat` 関数を使用します。
１つ目の引数には変換したい文字列を指定し、２つ目の引数には変換後の bit 数を指定します（bit 数に関わらず取得されるデータ型は float64 になります）。

**int 型**への変換には `ParseInt` 関数を使用します。
１つ目の引数には変換したい文字列を指定し、２つ目の引数には変換文字列の進数を指定し、３つ目の引数には変換後の bit 数を指定します（bit 数に関わらず取得されるデータ型は int64 になります）。

**uint 型**への変換には `ParseUint` 関数を使用します。
１つ目の引数には変換したい文字列を指定し、２つ目の引数には変換文字列の進数を指定し、３つ目の引数には変換後の bit 数を指定します（bit 数に関わらず取得されるデータ型は uint64 になります）。

**（２進数や 16 進数に変換する場合）**
２進数や 16 進数に変換する場合は、次のようになります。


```main.go
package main

import (
	"fmt"
	"strconv"
)

func main() {
	i, err := strconv.ParseInt("ffff", 16, 64)
	fmt.Printf("%T, %d\n", i, i) // int64, 65535
	fmt.Println(err)             // <nil>

	u, err := strconv.ParseUint("11111111", 2, 64)
	fmt.Printf("%T, %d\n", u, u) //uint64, 255
	fmt.Println(err)             // <nil>

	u2, err := strconv.ParseUint("42", 2, 64)
	fmt.Printf("%T, %d\n", u2, u2) // uint64, 0
	fmt.Println(err)  // strconv.ParseUint: parsing "42": invalid syntax
}
```

```:ターミナル（コマンドプロンプト）
C:\golang\sample>go build main.go
C:\golang\sample>.\main
int64, 65535
<nil>
uint64, 255
<nil>
uint64, 0
strconv.ParseUint: parsing "42": invalid syntax
```

最後の例は、`"42"` という数字を２進数に変換しようとしたが、`0`、`1` 以外の数字が含まれているため、エラーが発生した場合です。

### 3-2. 数値から文字列への変換

#### 3-2-1. 一般的な変換

数値から文字列への変換は、`Itoa` 関数を使用します（itoa とは「Integer to ASCII」の意です）。
コードを書くと、次のようになります。

```main.go
package main

import (
	"fmt"
	"strconv"
)

func main() {
	var num int = 100
	var str string = strconv.Itoa(num)
	fmt.Println(str)  // 100
}
```

```:ターミナル（コマンドプロンプト）
C:\golang\sample>go build main.go
C:\golang\sample>.\main
100
```

出力値の見た目は `100` ですが、文字列に変換されています。

#### 3-2-2. 詳細な変換指定

```main.go
package main

import (
	"fmt"
	"strconv"
)

func main() {
	s1 := strconv.FormatBool(true)
	fmt.Printf("%T, %s\n", s1, s1) // string, true

	s2 := strconv.FormatFloat(3.1415, 'E', -1, 64)
	fmt.Printf("%T, %s\n", s2, s2) // string, 3.1415E+00

	s3 := strconv.FormatInt(-42, 16)
	fmt.Printf("%T, %s\n", s3, s3) // string, -2a

	s4 := strconv.FormatUint(42, 16)
	fmt.Printf("%T, %s\n", s4, s4) // string, 2a
}
```
```:ターミナル（コマンドプロンプト）
C:\golang\sample>go build main.go
C:\golang\sample>.\main
string, true
string, 3.1415E+00
string, -2a
string, 2a
```

**bool 型**から **string 型**への変換には `FormatBool` 関数を使用します。

**float 型**から **string 型**への変換には `FormatFloat` 関数を使用します。
１つ目の引数には変換したい実数を指定し、２つ目の引数には浮動小数点数の書式を表す指定子を指定し、３つ目の引数には変換後小数点の桁数を指定し（`-1` は全て表示）、４つ目の引数には変換後の浮動小数点数の精度（bit 数）を指定します。

**int 型**から **string 型**への変換には `FormatInt` 関数を使用します。
１つ目の引数には変換したい整数を指定し、２つ目の引数には変換する数値の進数を指定します。

**uint 型**から **string 型**への変換には `FormatUint` 関数を使用します。
１つ目の引数には変換したい整数を指定し、２つ目の引数には変換する数値の進数を指定します。

詳しくは、下記のサイトが分りやすいです。

＜参考＞
[【Go入門】strconvパッケージ ～ 文字列型と数値型・論理型の相互変換](https://leben.mobi/go/strconv/package/)

# さいごに

Go を触り始めたばかりなので間違いもあるかと思います。
お気づきのことなどありましたら、ご指摘いただけると幸いです。