nがひとつ多い。

えぬなおの技術的なことを書いていくとこ。

【Go】Go言語で指数表記の文字列型を数値型にパースする。

指数表記の文字列型???何それ?

これ。

package main

import (
    "fmt"
)

func main() {
    fmt.Println(float64(1000000)) //-> 1e+06
}

play.golang.org

goで浮動小数点数型をデフォルトではフォーマットした時に 1e+06 のように出力され、こういう表記を指数表記、英語ではexponential notationnotationscientific notation とか呼ぶ。

それがどうしたの?

って感じだろうが少し待って欲しい、例えば以下のようなreturnScientificNotation関数的な何かでこの文字列を返されてしまった時、でも自分はその数値を int として使いたい場合はどうしようってのが今回の話題だ(こんな事起こるの?って感じだが僕の場合、jwtのtokenでこれが起きた)。

package main

import (
    "fmt"
    "reflect"
)

func returnScientificNotation() string{
    // なんかの処理
    return "1e+06"
}

func main() {
    str := returnScientificNotation() // 1000000が欲しいのになぜか指数表記の文字列型で返された
    dst := doSomeThing(str) // なんかやって元のint型に直したい
    if reflect.DeepEqual(1000000, dst) {
        fmt.Println("same")
    } else {
        fmt.Println("different")
    }
}

それの何が問題なの?

実は結構詰んでいる。 例えば文字列->整数は普通こういうパースは strconv.Atoi パッケージを使うのが定石だが、当然エラーする。

package main

import (
    "fmt"
    "strconv"
)


func main() {
    dst, err := strconv.Atoi("1e+06")
    if err != nil {
        panic(err)
    }
    fmt.Println(dst)
}
panic: strconv.Atoi: parsing "1e+06": invalid syntax

goroutine 1 [running]:
main.main()
    /tmp/sandbox701385143/main.go:13 +0x180

play.golang.org

それっぽい strconv のモジュールもない。

interfaceの型のキャストもだめ。

package main

import (
    "fmt"
)


func main() {
    var str interface{}
    str = "1e+06"
    dst, ok := str.(int)
    if !ok {
        panic("cannot cast")
    }
    fmt.Println(dst)
}
panic: cannot cast

goroutine 1 [running]:
main.main()
    /tmp/sandbox399888292/main.go:14 +0xc0

play.golang.org

どうしよ。

こうしよう。

package main

import (
    "fmt"
    "strconv"
    "reflect"
)

func parseScientificNotation(str string) (int, error) {
    float, err := strconv.ParseFloat(str, 64)
    if err != nil {
        return 0, err
    }

    return int(float), nil
}

func returnScientificNotation() string{
    // なんかの処理
    return "1e+06"
}

func main() {
    str := returnScientificNotation() // 1000000が欲しいのになぜか指数表記の文字列型で返された
    dst, _ := parseScientificNotation(str) // なんかやって元のint型に直したい
    if reflect.DeepEqual(1000000, dst) {
        fmt.Println("same") // -> same
    } else {
        fmt.Println("different")
    }
}

play.golang.org

かいせつ

ポイントは strconv.ParseFloat で string -> float64をした後で int型にキャストしている所で、 strconv.ParseFloat は実は "0.000001" のような表記の他、今回のような指数表記の文字列型にも対応している。

今回は冒頭で指数表記の文字列が float64() のマクロでキャストをしているので、指数表記の文字列がfloat系の型からキャストされた後に出力されてるから・・・と予想できやすいが、いざライブラリとかでいきなり 1e+06 と返されるとこの答えにまず行き着きにくい(という言い訳・・・・w)

ソースではここのファイルの genericFtoa(dst []byte, val float64, fmt byte, prec, bitSize int) []byte 関数を見ればパースの動きがわかるだろう。

golang.org

感想

まじで fmt.Sprint(float64(int型)) とかで返してくるライブラリやめろ。