LoginSignup
3
3

More than 5 years have passed since last update.

Goの浮動小数点数まわりの知っておいた方が良いこと

Posted at

Goの浮動小数点数の扱いについて非常にどーでも良いことに気が付いたので晒しておきます。何故そうなるのかまでは調べていません。2点あります。

1. マイナスゼロ(-0.0)を定数にできない

Goのソースコード中に「-0.0」を記入しても、ビルドした際にコンパイラが気を利かせて「+0.0」にしてしまうようです。
IEEE754では「-0.0」を表現できるはずなので、定数で表現できても良いと思うのですが。
いちおう、浮動小数点数のbitを直接弄れば「-0.0」を作ることが出来るようで、無理やり作った「-0.0」を用いて計算した結果には符号が引き継がれるみたいです。

サンプルコード

float-minus-zero.go
package main

import (
    "fmt"
    "math"
    "strconv"
)

func main() {
    fmt.Println("1 => ", strconv.FormatFloat(0.0, 'f', -1, 64))
    fmt.Println("2 => ", strconv.FormatFloat(-0.0, 'f', -1, 64))
    fmt.Println("3 => ", strconv.FormatFloat(0.0*-1.0, 'f', -1, 64))

    // -0.0
    f := math.Float64frombits(1 << 63)

    fmt.Println("4 => ", strconv.FormatFloat(f, 'f', -1, 64))
    fmt.Println("5 => ", strconv.FormatFloat(f*0.0, 'f', -1, 64))
    fmt.Println("6 => ", strconv.FormatFloat(f*-0.0, 'f', -1, 64))
    fmt.Println("7 => ", strconv.FormatFloat(f*f, 'f', -1, 64))
    fmt.Println("8 => ", strconv.FormatFloat(f*-1.0, 'f', -1, 64))
    fmt.Println("9 => ", strconv.FormatFloat(f*1.0, 'f', -1, 64))
}
1 =>  0
2 =>  0
3 =>  0
4 =>  -0
5 =>  -0
6 =>  -0
7 =>  0
8 =>  0
9 =>  -0

定数で表現できないというか、コンパイル時に評価が終わって結果がゼロだと符号が無くなるようですね。
※実行できます→https://play.golang.org/p/cNbP3bsPEL

ちなみに、適当にwandboxでC言語とC++言語を試したところ、「-0.0」を定数で表現出来るようでした。

float-minus-zero.c
#include "stdio.h"
int main(void) {
    printf("%f\n", -0.0);
    return 0;
}
-0.000000
float-minus-zero.cpp
#include <iostream>
int main() {
    std::cout << -0.0 << std::endl;
}
-0

2. strconv.AppendFloatで小数点以下桁数を指定すると劇的に遅くなる

strconv.AppendFloatやstrconv.FormatFloat1の小数点以下桁数を指定する引数にゼロ以上の値を入れてしまうと場合によっては10倍程度遅くなります。
速度が必要な場合は有効桁がそんなに要らなくても0以下を渡して、小数点をすべて出力するようにした方が良いです。

サンプルコード

append-float.go
func BenchmarkStrconvAppendFloat1(b *testing.B) {
    b.ReportAllocs()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        strconv.AppendFloat(nil, 3.1415926535, 'f', -1, 64)
    }
}
func BenchmarkStrconvAppendFloat2(b *testing.B) {
    b.ReportAllocs()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        strconv.AppendFloat(nil, 3.1415926535, 'f', 1, 64)
    }
}
func BenchmarkStrconvAppendFloat3(b *testing.B) {
    b.ReportAllocs()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        strconv.AppendFloat(nil, 2.225073858507201e-308, 'f', -1, 64)
    }
}
func BenchmarkStrconvAppendFloat4(b *testing.B) {
    b.ReportAllocs()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        strconv.AppendFloat(nil, 2.225073858507201e-308, 'f', 1, 64)
    }
}
func BenchmarkStrconvAppendFloat5(b *testing.B) {
    buf := make([]byte, 0, 1024)
    b.ReportAllocs()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        strconv.AppendFloat(buf, 2.225073858507201e-308, 'f', -1, 64)
    }
}
func BenchmarkStrconvAppendFloat6(b *testing.B) {
    buf := make([]byte, 0, 1024)
    b.ReportAllocs()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        strconv.AppendFloat(buf, 2.225073858507201e-308, 'f', 1, 64)
    }
}

適当にベンチマークを実行すると以下のようになります。

BenchmarkStrconvAppendFloat1-6           5000000               316 ns/op              24 B/op          2 allocs/op
BenchmarkStrconvAppendFloat2-6           3000000               401 ns/op               8 B/op          1 allocs/op
BenchmarkStrconvAppendFloat3-6           1000000              1661 ns/op            1016 B/op          7 allocs/op
BenchmarkStrconvAppendFloat4-6            100000             15213 ns/op               8 B/op          1 allocs/op
BenchmarkStrconvAppendFloat5-6           2000000               994 ns/op               0 B/op          0 allocs/op
BenchmarkStrconvAppendFloat6-6            100000             15148 ns/op               0 B/op          0 allocs/op

アロケート回数とかアロケート量とか関係なく劇的に遅いです。もはやバグと言って良いレベルなんじゃないでしょうか。

気が付いた理由

rapidjsonの高速な浮動小数点数文字列変換機能をGoに移植する際にたまたま気が付きました。移植したやつ→dtoa


  1. fmt.Printf系の書式指定の影響は確認してません。 

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3