Go: math: Exp(1.0) returns different values on amd64 vs arm64

Created on 10 May 2017  路  10Comments  路  Source: golang/go

foo.go:

package main

import (
    "fmt"
    "math"
)

func main() {
    fmt.Println(math.Exp(1.0))
}

prints:

amd64: 2.718281828459045
arm64: 2.7182818284590455

amd64:

$ go version
go version go1.8.1 darwin/amd64
$ go env
GOARCH="amd64"
GOBIN=""
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/Users/tamird/src/go"
GORACE=""
GOROOT="/Users/tamird/src/go1.8"
GOTOOLDIR="/Users/tamird/src/go1.8/pkg/tool/darwin_amd64"
GCCGO="gccgo"
CC="clang"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/sw/d6165ghx7jx3cdl7cggxzbx00000gn/T/go-build154970439=/tmp/go-build -gno-record-gcc-switches -fno-common"
CXX="clang++"
CGO_ENABLED="1"
PKG_CONFIG="pkg-config"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
$ go run foo.go
2.718281828459045

arm64:

$ go version
go version go1.8.1 linux/arm64
$ go env
GOARCH="arm64"
GOBIN=""
GOEXE=""
GOHOSTARCH="arm64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/root/go"
GORACE=""
GOROOT="/usr/lib/go-1.8"
GOTOOLDIR="/usr/lib/go-1.8/pkg/tool/linux_arm64"
GCCGO="gccgo"
CC="gcc"
GOGCCFLAGS="-fPIC -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build383633414=/tmp/go-build -gno-record-gcc-switches"
CXX="g++"
CGO_ENABLED="1"
PKG_CONFIG="pkg-config"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
$ go run foo.go
2.7182818284590455

There may be other inconsistencies lurking. Discovered in https://github.com/cockroachdb/cockroach/issues/14405.

FrozenDueToAge NeedsFix release-blocker

Most helpful comment

Also looking at #18354 seems like the consensus is that we don't want to promise that floating point functions will return exactly the same result on all platforms. The current status is that in many cases not all bits match between different platforms for many math routines.

All 10 comments

@griesemer

amd64 and s390x have assembly implementations, all the others architectures use a pure go routine and they implement different algorithms, so last-bit-mismatches are not unexpected.

CC @cldorian

Also looking at #18354 seems like the consensus is that we don't want to promise that floating point functions will return exactly the same result on all platforms. The current status is that in many cases not all bits match between different platforms for many math routines.

That's not a happy-making consensus. We've had good portable implementations of transcendentals (fdlibm) for decades now, and the same for FP<->string conversion for nearly that long.

We don't promise that the transcendentals are last-bit correct, especially for boundary cases. There is an open bug for Sin(2^80) or something like that. (We do promise that the float/string conversions are.) I admit that Exp(1) is not that much of a boundary case though. If there's a cheap improvement to one or the other to get the correctly rounded math.E out, that's fine. We're not looking for whole new algorithms for last-bit precise implementations though.

6794 is what I meant.

As noted above, while we're not guaranteeing last-bit correct for all inputs, I'm willing to assert that Exp(1) should be as precise as possible. So this seems reasonable as NeedsFix.

  1. The issue comes from difference between Go code and AMD64 assembly code. Result of Exp(1) in Go is ...455 and result of Exp(1) in AMD64 assembly is ...45. It is irrelevant to ARM64. Use GOARCH=386 go build on AMD64 machine and result will be ...455.
  2. A more precise value of Exp(1) is 2.71828182845904523536, and ...45 is the most precise float64 value.
  3. As said in comment of src/math/exp.go, the Go code is a simplified version of the original C(FreeBSD's /usr/src/lib/msun/src/e_exp.c). I tested the origin C code and its result is also ...455.
  4. The algorithm uses a polynomial to get the approximate value of Exp(x).
  5. If change P1(the first polynomial coefficient) to 1.0 / 6.0(the origin value in code is 0.166666666666666019037, float64 value is ...66602, 1.0 / 6.0 is ...66666), result of Exp(1) will be ...45. I did some very simple error statistic and can't observe difference introduced by using 1.0 / 6.0 as P1.
  6. Maybe this issue should be fixed by changing P1 to 1.0 / 6.0 in Go code but I am not sure this is the right way. Should we do some professional error statistic and how to do that?

CL https://golang.org/cl/49294 mentions this issue.

Was this page helpful?
0 / 5 - 0 ratings