Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
sarang2dan opened this issue May 27, 2024 · 3 comments
Labels
NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Milestone

Comments

@sarang2dan
Copy link

sarang2dan commented May 27, 2024

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
@sarang2dan
Copy link
Author

related issue: #20319

@mknyszek mknyszek added this to the Backlog milestone Jun 3, 2024
@mknyszek mknyszek added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Jun 3, 2024
@mknyszek
Copy link
Contributor

mknyszek commented Jun 3, 2024

CC @griesemer via https://dev.golang.org/owners

At first glance, I suspect this is one of those situations where the result is implementation-specific (https://go.dev/ref/spec#Floating_point_operators) per the spec. I don't think there's an explicit goal to precisely match C's floating point results, especially not across all architectures. But I'll let @griesemer respond who is more familiar with these details. @randall77 may also have some insights.

@randall77
Copy link
Contributor

I agree, there is no intent here that we match C's result.

If C's result is more correct, then maybe, but given that other architectures give the same result I don't think we would consider this a bug.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Projects
None yet
Development

No branches or pull requests

3 participants