LoginSignup
9
2

More than 5 years have passed since last update.

Go 言語の math.Sin は計算結果がおかしいことがある

Last updated at Posted at 2019-02-23

math.Sin

Go で三角関数を利用していたら、 math.Sin(x) の結果が 1 を超えることがあった。

調べて見ると、計算結果がおかしくなるのは引数が 1e15 などの巨大な数の場合のみみたいなので困ることは少ないと思う。

他言語との比較

各言語での計算結果を下表にまとめた。
"%.3g" で文字列化した結果が C++ と異なる場合に太字にしてある。

θ c++ ruby python3 node groovy go
2**1 0.909 0.909 0.909 0.909 0.909 0.909
2**40 -0.406 -0.406 -0.406 -0.406 -0.406 -0.406
2**41 0.742 0.742 0.742 0.742 0.742 0.742
2**42 0.995 0.995 0.995 0.995 0.995 0.995
2**43 -0.199 -0.199 -0.199 -0.199 -0.199 -0.199
2**44 0.39 0.39 0.39 0.39 0.39 0.39
2**45 0.718 0.718 0.718 0.718 0.718 0.719
2**46 0.999 0.999 0.999 0.999 0.999 0.999
2**47 -0.0649 -0.0649 -0.0649 -0.0649 -0.0649 -0.0673
2**48 0.129 0.129 0.129 0.129 0.129 0.134
2**49 0.257 0.257 0.257 0.257 0.257 0.266
2**50 0.496 0.496 0.496 0.496 0.496 0.513
2**51 0.862 0.862 0.862 0.862 0.862 0.912
2**52 0.874 0.874 0.874 0.874 0.874 0.792
2**53 -0.849 -0.849 -0.849 -0.849 -0.849 -0.967
2**54 0.897 0.897 0.897 0.897 0.897 0.492
2**55 -0.792 -0.792 -0.792 -0.792 -0.792 -0.816
2**56 0.967 0.967 0.967 0.967 0.967 1.09e+03
2**57 -0.493 -0.493 -0.493 -0.493 -0.493 1.79e+07
2**58 0.858 0.858 0.858 0.858 0.858 1.86e+11
2**59 0.882 0.882 0.882 0.882 0.882 1.63e+15
2**60 -0.831 -0.831 -0.831 -0.831 -0.831 1.36e+19
2**61 0.925 0.925 0.925 0.925 0.925 1.12e+23
2**62 -0.703 -0.703 -0.703 -0.703 -0.703 9.16e+26
2**63 1 1 1 1 1 1.04e+240
2**64 0.0236 0.0236 0.0236 0.0236 0.0236 3.38e+242

2**45 でずれ始め、2**56 で完全におかしくなっている。

src/math/sin.go

src/math/sin.go を見ると

Results may be meaningless for x > 2**49 = 5.6e14.

と書いてある。
meaningless な値を返すぐらいなら、仕様に有効な値の範囲を明記して、それ以外の入力に対しては非数を返してほしいと思う。

sin 以外の三角関数について

調べてみたら、sin, cos, tan は go は他の言語と違う結果になる感じ。
atan, atan2 は大丈夫そう。
asin, acos は、大きな入力を受け付けないので調べていない。

結果は以下の通り:

t = 1e+12

処理系 sin(t) cos(t) tan(t) atan(t) atan2(t,t/2)
c++ -0.611 0.791 -0.772 1.57 1.11
node -0.611 0.791 -0.772 1.57 1.11
python3 -0.611 0.791 -0.772 1.57 1.11
groovy -0.611 0.791 -0.772 1.57 1.11
go -0.611 0.791 -0.772 1.57 1.11

t = 1e+14

処理系 sin(t) cos(t) tan(t) atan(t) atan2(t,t/2)
c++ -0.209 -0.978 0.214 1.57 1.11
node -0.209 -0.978 0.214 1.57 1.11
python3 -0.209 -0.978 0.214 1.57 1.11
groovy -0.209 -0.978 0.214 1.57 1.11
go -0.217 -0.976 0.222 1.57 1.11

t = 1e+16

処理系 sin(t) cos(t) tan(t) atan(t) atan2(t,t/2)
c++ 0.78 -0.626 -1.25 1.57 1.11
node 0.78 -0.626 -1.25 1.57 1.11
python3 0.78 -0.626 -1.25 1.57 1.11
groovy 0.78 -0.626 -1.25 1.57 1.11
go 0.969 0.248 3.91 1.57 1.11

t = 1e+308

処理系 sin(t) cos(t) tan(t) atan(t) atan2(t,t/2)
c++ 0.453 -0.891 -0.509 1.57 1.11
node 0.453 -0.891 -0.509 1.57 1.11
python3 0.453 -0.891 -0.509 1.57 1.11
groovy 0.453 -0.891 -0.509 1.57 1.11
go Inf -Inf NaN 1.57 1.11

まあ、1e16 を超える数を sin, cos, tan に入れても意味が無いのはわかるけど、sin, cos なら絶対値が 1 以下の値がかえってきてほしいと思う。あと、$(sin(t))^{2} + (cos(t))^{2}$ は (多少の誤差は許容するけど) 1 になってほしい。

実行速度

go の sin, cos は独自実装のようなので、実行速度がちょっと気になった。

で。
測ってみた。

C++ のソースコード

c++
#include <iostream>
#include <iomanip>
#include <cmath>

double run(int size){
  double sum = 0;
  for( int y=0 ; y<size ; ++y ){
    for( int x=0 ; x<size ; ++x ){
      sum += std::sin(x+y+sum);
      sum += std::cos(x-y+sum);
    }
  }
  return sum;
}

int main(){
  int size;
  std::cin >> size;
  std::cout << std::setprecision(20) << run(size) << std::endl;
  return 0;
}

go のソースコード

go
package main

import (
    "bufio"
    "fmt"
    "math"
    "os"
    "strconv"
)

func run(size int) float64 {
    sum := 0.0
    for y := 0; y < size; y++ {
        for x := 0; x < size; x++ {
            sum += math.Sin(float64(x+y) + sum)
            sum += math.Cos(float64(x-y) + sum)
        }
    }
    return sum
}

func main() {
    stdin := bufio.NewScanner(os.Stdin)
    stdin.Scan()
    text := stdin.Text()
    size, _ := strconv.ParseInt(text, 10, 32)
    fmt.Printf("%.20f\n", run(int(size)))
}

測定

echo 5000|time a.out

みたいな感じ。user と real がほぼ一致し、sys がゼロだったので、user の値のみを表にまとめた。

測定結果

コンパイラ 最適化オプション 時間
clang++ -O0 2.24
clang++ -O1 2.13
clang++ -O2 2.14
clang++ -O3 2.14
clang++ -Ofast 2.14
g++-8 -O0 2.21
g++-8 -O1 2.19
g++-8 -O2 2.15
g++-8 -O3 2.14
g++-8 -Ofast 2.21
go -gcflags '-N -l' 1.70
go 1.60

※ C++ のコンパイルオプションは他に「-mtune=native -march=native」を指定している

go は独自実装によって高速化を実現したのかな。
この実験だけではっきりしたことは言えないけれど。

9
2
1

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
9
2