Skip to content

math: math.Mod(Exp(63.5)*10000.0, 100.0) return different values between C exp() #67662

@sarang2dan

Description

@sarang2dan

Go version

go version go1.22.3 darwin/arm64

Output of go env in your module/workspace:

GO111MODULE='on'
GOARCH='arm64'
GOBIN=''
GOCACHE='/Users/sonumb/Library/Caches/go-build'
GOENV='/Users/sonumb/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMODCACHE='/Users/sonumb/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPATH='/Users/sonumb/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/local/go'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/local/go/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.22.3'
GCCGO='gccgo'
AR='ar'
CC='clang'
CXX='clang++'
CGO_ENABLED='1'
GOMOD='/dev/null'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/7n/2904fqr55nv8px1km09jjnmw0000gp/T/go-build968614564=/tmp/go-build -gno-record-gcc-switches -fno-common'

What did you do?

a.go

package main

/*
#include <stdlib.h>
#include <math.h>
*/

import "C"

//export CExp
func CExp(x float64) float64 {
    ret := C.exp(C.double(x))  // This result are same with result of math.Exp() on amd64/darwin.
    return float64(ret)
}

b.go

package main

import (
	"fmt"
	"math"
)

//----------------------------------------------------------------
// same with math/exp.go
func GoExp(x float64) float64 {
	const (
		Ln2Hi = 6.93147180369123816490e-01
		Ln2Lo = 1.90821492927058770002e-10
		Log2e = 1.44269504088896338700e+00

		Overflow  = 7.09782712893383973096e+02
		Underflow = -7.45133219101941108420e+02
		NearZero  = 1.0 / (1 << 28) // 2**-28
	)

	// special cases
	switch {
	case math.IsNaN(x) || math.IsInf(x, 1):
		return x
	case math.IsInf(x, -1):
		return 0
	case x > Overflow:
		return math.Inf(1)
	case x < Underflow:
		return 0
	case -NearZero < x && x < NearZero:
		return 1 + x
	}

	// reduce; computed as r = hi - lo for extra precision.
	var k int
	switch {
	case x < 0:
		k = int(Log2e*x - 0.5)
	case x > 0:
		k = int(Log2e*x + 0.5)
	}
	hi := x - float64(k)*Ln2Hi
	lo := float64(k) * Ln2Lo

	// compute
	return expmulti(hi, lo, k)
}


func expmulti(hi, lo float64, k int) float64 {
	const (
		P1 = 1.66666666666666657415e-01  /* 0x3FC55555; 0x55555555 */
		P2 = -2.77777777770155933842e-03 /* 0xBF66C16C; 0x16BEBD93 */
		P3 = 6.61375632143793436117e-05  /* 0x3F11566A; 0xAF25DE2C */
		P4 = -1.65339022054652515390e-06 /* 0xBEBBBD41; 0xC5D26BF1 */
		P5 = 4.13813679705723846039e-08  /* 0x3E663769; 0x72BEA4D0 */
	)

	r := hi - lo
	t := r * r
	c := r - t*(P1+t*(P2+t*(P3+t*(P4+t*P5))))
	y := 1 - ((lo - (r*c)/(2-c)) - hi)
	// TODO(rsc): make sure Ldexp can handle boundary k
	return math.Ldexp(y, k)
}
//----------------------------------------------------------------

func main() {
	var a float64 = 63.5

	// C.exp()
	ret := CExp(a) * 10000.0
	fmt.Println(ret, math.Mod(ret, 100.0))

	// math.Exp() true haveArchExp
	ret = math.Exp(a) * 10000.0
	fmt.Println(ret, math.Mod(ret, 100.0))

	// math.Exp() false haveArchExp
	ret = GoExp(a) * 10000.0
	fmt.Println(ret, math.Mod(ret, 100.0))
}

What did you see happen?

We expected the results of the exp() function from the C library and Go's math.Exp() to be the same on arm64/darwin. However, they were not.

$ go run a.go b.go
3.7818090853912896e+31 84    # same result in amd64/darwin 
3.78180908539129e+31 80
3.78180908539129e+31 80

Python result on arm64/darwin was below.

# builtin math
$ python3 -c "import math; print((math.exp(63.5)*10000.0)%100.0)"
84.0

# numpy
$ python3 -c "import numpy as np; print((np.exp(63.5)*10000)%100)"
84.0

What did you expect to see?

I think that when the OS and architecture are different, the results of functions can vary. However, at the very least, the consistency of the results of functions in the 'math' package should be guaranteed, regardless of the OS or architecture.

run above codes on "arm64/darwin"

$ go run a.go b.go
3.7818090853912896e+31 84    # same result in amd64/darwin 
3.7818090853912896e+31 84
3.7818090853912896e+31 84

Metadata

Metadata

Assignees

No one assigned

    Labels

    FrozenDueToAgeNeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions