20190927のGoに関する記事は10件です。

golang-migrate/migrateパッケージを使ってみる

Go言語勉強継続中。

SQLite3を埋め込みで使うことを想定し、バージョンアップなどに伴うマイグレーションを外部ファイルなどに依存せずに行えるパッケージを探していた。
golang-migrate/migrateの評判がよさそうだったのだが、外部に依存しないデータソースとしてはgo-bindata(既にメンテナンスがなされてないようであまり使われていないらしい)しかなかったので、string配列をデータソースとするsource.Driverを書いてみることにした。

package main

import (
    "database/sql"
    "errors"
    "io"
    "io/ioutil"
    "log"
    "os"
    "strings"

    "github.com/golang-migrate/migrate"
    "github.com/golang-migrate/migrate/database"
    "github.com/golang-migrate/migrate/database/sqlite3"
    "github.com/golang-migrate/migrate/source"

    _ "github.com/mattn/go-sqlite3"
)

var sqls = []string{
    `CREATE TABLE T1 (F1, F2)`,
    `ALTER TABLE T1 ADD F3`,
    `ALTER TABLE T1 ADD F4`,
    `ALTER TABLE T1 ADD F5`,
}

func main() {
    var err error
    var db *sql.DB
    db, err = sql.Open("sqlite3", "./test.sqlite")
    if err != nil {
        log.Println(err)
    }
    defer db.Close()

    var dbdriver database.Driver
    dbdriver, err = sqlite3.WithInstance(db, &sqlite3.Config{MigrationsTable: "", DatabaseName: "test.sqlite"})
    if err != nil {
        log.Println(err)
    }

    var srcdriver source.Driver
    srcdriver, err = WithInstance(sqls)
    if err != nil {
        log.Println(err)
    }

    var m *migrate.Migrate
    m, err = migrate.NewWithInstance("strings", srcdriver, "sqlite3", dbdriver)
    if err != nil {
        log.Println(err)
    }

    err = m.Migrate(uint(len(sqls) - 1))
    if err != nil && err != migrate.ErrNoChange {
        log.Println(err)
    }

    ...
}

var notImplementedError = errors.New("not implemented")

type StringDriver []string

func WithInstance(sqls []string) (source.Driver, error) {
    ss := StringDriver(sqls)
    return &ss, nil
}

func (a *StringDriver) Open(url string) (source.Driver, error) {
    return nil, notImplementedError
}

func (a *StringDriver) Close() error {
    return nil
}

func (a *StringDriver) First() (version uint, err error) {
    if len(*a) > 0 {
        return 0, nil
    }
    return 0, os.ErrNotExist
}

func (a *StringDriver) Prev(version uint) (prevVersion uint, err error) {
    return 0, notImplementedError
}

func (a *StringDriver) Next(version uint) (nextVersion uint, err error) {
    if version+1 < uint(len(*a)) {
        return version + 1, nil
    }
    return 0, os.ErrNotExist
}

func (a *StringDriver) ReadUp(version uint) (r io.ReadCloser, identifier string, err error) {
    return ioutil.NopCloser(strings.NewReader((*a)[version])), "", nil
}

func (a *StringDriver) ReadDown(version uint) (r io.ReadCloser, identifier string, err error) {
    return nil, "", notImplementedError
}

埋め込み用途で使うことしか考えていないし、戻すことも考えていないのでPrevなどは未実装です。
あとは機能を増やす時にsqlsに足していけばいけます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go の -race option は内部で何をしているのか。何を検知しないのか。

-race をつけてCIを通しているのにAPIがデータ競合で落ちてしまいました。調べていたら-raceがそもそも何をしているかに行き着いたので簡単に共有します。

-race とは

-raceはコンパイラフラッグの一種で競合を検知するのに便利です。例えば下記のコードはmapへの読み書きが同時に起こってパニックするコードです。これをgo run main.go -raceのように実行するとwarningを出してくれます。

package main

import (
    "fmt"
    "strconv"
    "time"
)

func main() {

    m := make(map[string]int)

    go func() {
        for i := 0; i < 1000; i++ {
            m[strconv.Itoa(i)] = i // write
        }
    }()

    go func() {
        for i := 0; i < 1000; i++ {
            fmt.Println(i, m[strconv.Itoa(i)]) // read
        }
    }()

    time.Sleep(time.Second * 5)

}

下記のように実行するとWarningを出します。

$ go run -race main.go

-race はそもそも何をしているのか

内部ではC/C++用の競合検出ライブラリが使われています。簡単に言うと、競合を実行時に検出するコードを出力することができるライブラリです。
https://github.com/google/sanitizers/wiki/ThreadSanitizerCppManual

もちろんGoのコンパイラはこれをサポートしており、実際にruntime/raceREADME.mdにはThreadSanitizerが使われていることが明記されています。

https://github.com/golang/go/tree/master/src/runtime/race

untime/race package contains the data race detector runtime library. It is based on ThreadSanitizer race detector, that is currently a part of the LLVM project (http://llvm.org/git/compiler-rt.git).

コンパイル時に実際にどのようなコードが仕込まれているかを見てみましょう。例えば下記のコードをビルドした場合をみてみます。

package main

func Inc(x *int) {
    *x++
}

普通にビルドすると下記のようにコンパイルされます。

...

pcdata  $2, $1
pcdata  $0, $1
movq    "".x+8(SP), AX
pcdata  $2, $0
incq    (AX)

...

-race をつけると下記のようにコンパイルされます。

...

pcdata  $2, $1
movq    "".x+32(SP), AX
testb   AL, (AX)
pcdata  $2, $0
movq    AX, (SP)
call    runtime.raceread(SB)
pcdata  $2, $1
movq    "".x+32(SP), AX
movq    (AX), CX
movq    CX, ""..autotmp_4+8(SP)
pcdata  $2, $0
movq    AX, (SP)
call    runtime.racewrite(SB)
movq    ""..autotmp_4+8(SP), AX
incq    AX
pcdata  $2, $2
pcdata  $0, $1
movq    "".x+32(SP), CX

...

コンパイラーがcall runtime.raceread(SB)のように同時に到達可能な各メモリー位置に読み取りおよび書き込みを検知する命令を追加しています。ご覧の通り、-raceをつけると命令が増えてパフォーマンスが落ちるのでビルドしたバイナリを本番に乗っけるのはやめましょう。go build -race でプログラムが遅くなってた話

ちなみにGoコンパイラが吐き出すアセンブリはCompiler Explorerというサービスで簡単に確認できて便利です。

Compiler Explorer

-raceを使ってるのに競合が起きる理由

-raceでは競合を検知するための命令を追加しているので、そこに到達しないと競合を検知しません。つまりテスト時に-raceをつけてPASSしたからと言って競合が発生しないとは言えません。

Goのドキュメントにも明示されていました。
https://golang.org/doc/articles/race_detector.html#How_To_Use

To start, run your tests using the race detector (go test -race). The race detector only finds races that happen at runtime, so it can't find races in code paths that are not executed. If your tests have incomplete coverage, you may find more races by running a binary built with -race under a realistic workload.

テストカバレッジが低い場合はテストだけで全ての競合が消せたと判断せずに、実際に-race をつけてビルドしてみて動かしてみることが大切です。

Referece

Golang race detection
https://krakensystems.co/blog/2019/golang-race-detection

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Golang - ディレクトリ構成と独自パッケージのimportについて

追記:GoModules(バージョン管理)を使用した場合の方法を整理しましたのでGoのバージョンが1.11以上の方はそちらを参照してください
Golang - Go Modulesで開発環境の用意する

はじめに

環境作成の際に、深く考えず環境変数 $GOPATH~/go を設定していましたが、作業していると自身で定義したパッケージの参照でエラーが発生し、保存時にフォーマットかけるようにしてるためimportが消えたりと思ったような動作をしなかったため構成を見直すことにしました。

GOPATH

パッケージをimportするときの解決先や、go getgo installをしたときのパッケージやバイナリのインストール先がGOPATHで指定したディレクトリになります。GOPATH配下は以下の三つのディレクトリで構成されます。

ちなみにGOPATHが未指定の場合は$HOME/goになります。

./GOPATH
├── ./bin    // 実行ファイルが格納されるディレクトリ(go installコマンドで自動生成されます)
├── ./pkg    // ビルドしたパッケージオブジェクトが格納されるディレクトリ(go buildをはじめとする各種コマンドで自動生成されます)
└── ./src    // パッケージごとのソースコードを配置するディレクトリ

ディレクトリ構成

src配下にプロジェクトのディレクトリを配置することろまでは間違いなさそうですが、どうやらsrc/github.com/username/projectで作成するのが良さそうです。
ずっと悩んでいたのですが以下の記事のおかげで解決できました!!

独自パッケージのインポート

githubに公開されている外部パッケージを取得する際にgo get github.com/XXXXXXコマンドで取得できますが
そうするとディレクトリの構成が上記のsrc/github.com/username/repositoryになるようですね!

ということでできた構成が以下の通りです
プロジェクトのディレクトリ配下は Standard Go Project Layout を参考にしました。

└── GolangProjects                          // GOPATHに設定
     ├── bin
     ├── pkg
     └── src
         └── github.com
             └── github-username            // githubのユーザー
                 └── project-sample         // プロジェクトごとのディレクトリ
                     ├── Makefile
                     ├── api
                     ├── assets
                     ├── build
                     ├── cmd
                     ├── configs
                     ├── deployments
                     ├── docs
                     ├── examples
                     ├── githooks
                     ├── init
                     ├── internal
                     ├── pkg
                     ├── scripts
                     ├── test
                     ├── third_party
                     ├── tools
                     ├── vendor
                     ├── web
                     └── website

import

上記の構成にした場合、独自パッケージのimportは以下の通りです

import "github.com/github-username/project-sample/パッケージ名"

いくつか参考にgithubで公開されているフレームワークを見てみましたが上記の通りになっていました

Docker環境の場合

私は以下の環境で開発を行おうかと思っているのですが、そこでもう一点ハマりました。

Golang - DockerとVSCodeで開発環境を用意する

上記構成のproject-sampleで
Remote-Containers: Add Development Container Configuration Files
してしまうと以下の理由から独自パッケージのimportでエラーが発生しました

  • 仮想環境上のワークスペースがproject-sample以下になってしまう (github.com/user-name/が参照できない)

リポジトリに反映してgo getすれば、GOPATHにパッケージがインストールされるのでエラーは解消しますが、それはかなりめんどくさそうと思ったので以下の通りに対応することでとりあえずエラーは回避できました

  • GolangProjectsディレクトリで
    Remote-Containers: Add Development Container Configuration Files

  • Docker上のGOPATHにローカルのGOPATHをマウント(workspaceFolderも合わせて変更しています)

devcontainer.json
    "workspaceFolder": "/go",
    "workspaceMount": "src=ローカルの$GOPATH,dst=/go,type=bind",

今回かなり手探りで調査したので誤っている点や、もっとこういう構成の方がいいよー!って構成があればぜひ教えていただきたいです!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go deferとpanicと俺

package main

import (
    "fmt"
)

func main() {

    defer fmt.Println("①最初に書いたけど、これが最後")

    // 関数も指定できるよ
    defer deferFunc()


    // これは無名関数だよ
    defer func() {
        fmt.Println("③deferって複数指定するとスタックなんだね")
    }()

    // panicから復旧してみるよ
    defer func() {
        e := recover()
        fmt.Println("④recover関数で復旧するよ:" , e)
    }()

    panic("ぱにっぱにっ。 ぱにっぱにっ。 ぱにっぱにっぱにっく。")

}

func deferFunc() {
  defer fmt.Println("②2番目に書いたけど、これが①より先")
}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

オブジェクト関係マッピング(gormを使ってみた話)

入社1年目の渡邉です。

最近学んだオブジェクト関係マッピング(ORM:Object-relational mapping)が、
すげぇ分かりやすいって時代遅れながら書かせて頂きます。

いつの時代も新しいことを考える人は凄いなと思います(笑)

最近はGo言語をよく書いてるので、Go言語でORを触れるgormを使います。

SQL文を意識した場合

DDL.sql
CREATE TABLE users (
    age         INTEGER,
    name        TEXT
);
db.go
import (
    "database/sql"
    "go/build"
    "io/ioutil"
    "strings"
)

func CreateTable(db *sql.DB) {
    execFile(db, "./DDL.sql")
}

func execFile(db *sql.DB, path string) {
    statements, _ := ioutil.ReadFile(build.Default.GOPATH + path)
    for _, statement := range strings.Split(string(statements), ";\n") {
        _, err := db.Exec(statement)
        if err != nil {
            panic(err.Error())
        }
    }
}

Tableを新規に作成したいところで CreateTable() を実行するようにします。

※DDL: https://wa3.i-3-i.info/word15639.html

ORMの場合

db.go
import (
    "time"

    "github.com/jinzhu/gorm"
)

type Users struct {
    age  int
    name string
}

func CreateTable(db *gorm.DB) {
    db.CreateTable(&Users{})
}

Tableを新規に作成したいところで CreateTable() を実行するようにします。
個人的には直感かつ短いコードで書けて分かりやすい。

感想

gorm、めっちゃ分かりやすいなと使い方を知ってから思います。

・ORMを使うのか?
・SQL文を扱うのか?

メリット・デメリットに関してはまだまだ詰めきれてませんが、使っていって学びを深めようと思います。

関連記事

【GORM】Go言語でORM触ってみた(2019/09/05更新)
https://qiita.com/chan-p/items/cf3e007b82cc7fce2d81

諸説あり

O/Rマッピングは百害あって一利なし!(2018年11月17日に更新)
https://qiita.com/gomiryo/items/6d448c500749f91242d2

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

オブジェクト関係マッピング(gormを使ってみた)

入社1年目の渡邉です。

最近学んだオブジェクト関係マッピング(ORM:Object-relational mapping)が、
すげぇ分かりやすいって時代遅れながら書かせて頂きます。

いつの時代も新しいことを考える人は凄いなと思います(笑)

最近はGo言語をよく書いてるので、Go言語でORを触れるgormを使います。

SQL文を意識した場合

DDL.sql
CREATE TABLE users (
    age         INTEGER,
    name        TEXT
);
db.go
import (
    "database/sql"
    "go/build"
    "io/ioutil"
    "strings"
)

func CreateTable(db *sql.DB) {
    execFile(db, "./DDL.sql")
}

func execFile(db *sql.DB, path string) {
    statements, _ := ioutil.ReadFile(build.Default.GOPATH + path)
    for _, statement := range strings.Split(string(statements), ";\n") {
        _, err := db.Exec(statement)
        if err != nil {
            panic(err.Error())
        }
    }
}

Tableを新規に作成したいところで CreateTable() を実行するようにします。

※DDL: https://wa3.i-3-i.info/word15639.html

ORMの場合

gorm を入れておきましょう

$ go get github.com/jinzhu/gorm

dbファイルの中身

db.go
import (
    "time"

    "github.com/jinzhu/gorm"
)

type Users struct {
    age  int
    name string
}

func CreateTable(db *gorm.DB) {
    db.CreateTable(&Users{})
}

Tableを新規に作成したいところで CreateTable() を実行するようにします。
個人的には直感かつ短いコードで書けて分かりやすい。

感想

gorm、めっちゃ分かりやすいなと使い方を知ってから思います。

・ORMを使うのか?
・SQL文を扱うのか?

メリット・デメリットに関してはまだまだ詰めきれてませんが、使っていって学びを深めようと思います。

関連記事

【GORM】Go言語でORM触ってみた(2019/09/05更新)
https://qiita.com/chan-p/items/cf3e007b82cc7fce2d81

GORMというORMが超絶便利だった件
https://medium.com/@taka.abc.hiko/gorm%E3%81%A8%E3%81%84%E3%81%86orm%E3%81%8C%E8%B6%85%E7%B5%B6%E4%BE%BF%E5%88%A9%E3%81%A0%E3%81%A3%E3%81%9F%E4%BB%B6-8d279489c38f

諸説あり

O/Rマッピングは百害あって一利なし!(2018年11月17日に更新)
https://qiita.com/gomiryo/items/6d448c500749f91242d2

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

即時関数 使用例 (Go言語で)

はじめに

入門書で言語の基礎を学んでいる時に即時関数ってどんな時に使うのかピンと来なかったのですが、インターンで使う機会があったので残しておこうと思います。
実際はJavaScriptを書いている時に使ったのですが、慣れる為にGo言語で書き換えました。

変数の初期値を引数によって変える

変数を作って、if,switch等で分岐して作られている変数に代入は面倒だなぁ~って時に使いました。

main.go
package main

import "fmt"

func main() {
    country := "日本"
    greeting := (func(country string) string {
        switch country {
        case "日本":
            return "こんにちは"
        case "アメリカ":
            return "Hello"
        default:
            return "xxxxx"
        }
    })(country)

    fmt.Println(country)    // 日本
    fmt.Println(greeting)   // こんにちは
}

おわりに

変数の初期値を引数によって変えようって時の書き方ってこれ良いのでしょうか?即時関数を使っていい場面なのか?
初心者すぎる疑問ばかりです。もっとコードを書いていき最適を考えられるようになりたいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Go, golang】Microsoft ExcelでもNumbersでもcsvファイルを文字化けしないように作成する。

前置き

Goでcsvを扱う記事はQiitaでも色々ありますが、半分自分のメモ代わりということで。
Microsoft Excelでも、Numbersでもcsvファイルを開いた時、文字化けしないファイルを作って出力するようにします。
サンプルは、比較的見たこともあるものかと思います。

環境

go version: 1.11.4
os: MacOS

サンプルコード

package main

import (
       "encoding/csv"
       "os"
       "log"
       "bufio"
)

func main() {
        // ファイルがあれば開く、なければ新しく作成するようにしています。
        file, err := os.OpenFile("new_company.csv", os.O_RDWR|os.O_CREATE, 0755)
        if err != nil {
                log.Fatal(err)
        }

        // Excelでも見れるようにするには、BOM付きUTF8にする必要があるので、fileの先頭にBOMを付与。
        bw := bufio.NewWriter(file)
        bw.Write([]byte{0xEF, 0xBB, 0xBF})

        // csvデータ
        records := [][]string{
                {"id", "name", "address"},
                {"1", "Softbank", "日本のどこか"},
                {"2", "au", "多分日本のどこか"},
                {"3", "Docomo", "おそらく日本のどこか"},
        }

        w := csv.NewWriter(bw)
        // w.Writeで1行づつやってもいいですが、一括でデータを取り込みできるので、今回はWriteAllにしました。
        // 必要に応じて使ってください。なお、WriteAllの場合、w.Flush()をしなくて良いのでわずかですがコード短縮になる(...?)
        w.WriteAll(records)

        if err := w.Error(); err != nil {
           log.Fatal(err)
        }

        // 最後にfileを閉じます。
        if err := file.Close(); err != nil {
                log.Fatal(err)
        }
}

個人的ポイント

bufioを使ってBOMの付与

https://qiita.com/bbq-all-stars/items/03fa27eeb6f46b580525

https://pinzolo.github.io/2017/03/29/utf8-csv-with-bom-on-golang.html
を参考にしつつ試してみました。

やはりcsvデータとなるデータの前にBOMを付与するところでしょうか。

どんな感じになったか

上記サンプルコードで、go run main.goしてファイルを生成した後、
lessコマンドで中身をみてみると、

<U+FEFF>id,name,address
1,Softbank,日本のどこか
2,au,多分日本のどこか
3,Docomo,おそらく日本のどこか
new_company.csv (END)

のように、先頭にBOMが付いているのがわかります(catコマンドだと付いているかわからない)。

Cot Editorでファイルを開いてみると...
スクリーンショット 2019-09-27 2.44.37.png
エンコーティングのところが「Unicode(UTF-8)B...」(これ、BOM付きってことです。見難いですが...)となっていて、BOM付きなことがわかります。

MacのMicrosoft Excelで開いてみると、最初にテキストファイルウィザードのウィンドウは表示されますが、
区切り文字の取り扱いとかを選んで「完了」を押すと、
スクリーンショット 2019-09-27 2.47.57.png
こんな感じでみることができます。...よかった。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【川原のGo言語&Elixir奮闘記】#3 今日はGoで並行機能遊び

Hello world私だよ。

前回でHello worldまで行けたよ。

今日はGoの並行機能で遊んでいくよ。

以下のプログラムを作るよ。

package main

import (
    "fmt"
    "time"
)

func readword(ch chan string) {
    fmt.Println("なんか入力して~")
    var word string
    fmt.Scanf("%s", &word)
    ch <- word
}

func timeout(t chan bool) {
    time.Sleep(10 * time.Second)
    t <- true
}

func main() {
    t := make(chan bool)
    go timeout(t)

    ch := make(chan string)
    go readword(ch)

    select {
    case word := <-ch:
        fmt.Println("承り~", word)
    case <-t:
        fmt.Println("はい、時間切れ~")
    }
}

ここで重要なのは、"go"ルーチンと"select”っていう文だよ。

両方の"go"ルーチンからの戻り値を"select”で受け付けているよ。

これを使うことによってプログラム上で非同期を表しているよ。

今日はGoの並行機能で遊んだよ。

最近、左目の上裏がちょっと痛いよ。

また来週。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

go で float32 を JSON にするとちょっと意外なことになる

まあタイトルのとおり。
ちょっと意外なことがあったので。

go の場合

まずはソースコード:

package main

import (
    "encoding/json"
    "fmt"
)

type Hoge struct {
    Foo float32
    Bar float64
}

func main() {
    hoge := Hoge{Foo: 0xa0000000, Bar: 0xa0000000}
    j, _ := json.Marshal(hoge)
    fmt.Println(string(j))
}

実行すると、出力は以下のようになる:

{"Foo":2684354600,"Bar":2684354560}

ぱっとみわかりにくいけれど、 float32 に由来する Foo は下二桁が 00 なのに対し、float64 に由来する Bar は、下二桁が 60 になっている。

同じ 0xa0000000 を入れているのに違う値になる。困る。

C# の場合

こちらもソースコードから。

using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;

[DataContract]
public class Hoge
{
  [DataMember]
  public float foo { get; set; }
  [DataMember]
  public double bar { get; set; }
}

internal class JsonSerializer
{
  public static DataContractJsonSerializer Serializer<t>()
  {
    System.Type type = typeof(t);
    return new DataContractJsonSerializer(type);
  }
}

partial class Program
{
  static void Main(string[] args)
  {
    var t = new Hoge();
    t.foo = 0xa0000000L;
    t.bar = 0xa0000000L;
    using (var fs = System.Console.OpenStandardOutput())
    {
      JsonSerializer.Serializer<Hoge>().WriteObject(fs, t);
    }
  }
}

結果は次の通り:

{"bar":2684354560,"foo":2.68435456E+09}

出力する書式は異なるものの、実質的に同じ値になっている。
これなら困らない。

Java + Gson の場合

やっぱりソースコードから。

import com.google.gson.Gson;

class Hoge {
  public float foo;
  public double bar;
}

public class Main {
  public static void main(String[] args) {
    Hoge hoge = new Hoge();
    hoge.foo = 0xa0000000L;
    hoge.bar = 0xa0000000L;
    Gson gson = new Gson();
    System.out.println(gson.toJson(hoge));
  }
}

Java には標準となる JSON Serializer がない模様なので Gson を使ってみた。

出力結果は:

{"foo":2.68435456E9,"bar":2.68435456E9}

出力形式も値も同じ。
困らない。

node.js の場合

ここでもまずはソースコード:

o = {
  foo: new Float32Array([0xa0000000]),
  bar: new Float64Array([0xa0000000])
}
process.stdout.write(JSON.stringify(o));

出力はこうなる:

{"foo":{"0":2684354560},"bar":{"0":2684354560}}

Float32Array とかって配列にならないんだへー、みたいな気持ちにはなるが、Float32Array でも Float64Array でもおなじになっている。

まとめ

32bit および 64bit の浮動小数点型変数に 0xa0000000 を入れて、それを JSON にしてみた。
結果は下表の通り。

言語など JSON内の表現 JSON内の表現を16進数で表現したもの
go float32 2684354600 0xa0000028
go float64 2684354560 0xa0000000
C# float 2.68435456E+09 0xa0000000
C# double 2684354560 0xa0000000
Java+Gson float 2.68435456E9 0xa0000000
Java+Gson double 2.68435456E9 0xa0000000
node.js Float32Array 2684354560 0xa0000000
node.js Float64Array 2684354560 0xa0000000

go で float32 型の値を JSON にした場合だけ、0xa0000000 にならない。
困る。

対策は「float32 を使わない」なんだけど、float32 をわざわざ使っているんだからなんか理由があるに違いなくて、簡単にはやめられないよね。
どうしたものか。

追記

というわけで、ライブラリ書いた。

see https://github.com/nabetani/marshaljsonf64

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む