Help us shape the future of Go! Take the 2026 Go Developer Survey

detmath

package
v1.0.0-alpha.26 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 25, 2026 License: MIT Imports: 3 Imported by: 0

Documentation

Overview

Package detmath holds deterministic, architecture-independent ports of the Go standard library's transcendental kernels: Exp, Expm1, Log, Log1p, Sin, Cos, Tan, Atan2, Asin, Pow, Erf and Erfinv — plus the ziggurat standard normal and exponential variates, NormFloat64 and ExpFloat64.

Why it exists

The standard library's results for these functions depend on the machine, not just the input:

  • math.Exp is assembly on amd64 and arm64, and the amd64 routine picks an FMA path at run time when the CPU supports it (exp_amd64.go: useFMA), so two amd64 machines can disagree.
  • math.Log is assembly on amd64 and pure Go on arm64.
  • The pure-Go Sin / Cos / Tan / Atan / Asin / Pow / Expm1 / Erf / Erfinv are compiled with fused multiply-add contraction on arm64 and without it on amd64; math.Erf and math.Erfinv also call the machine-specific math.Exp / math.Log internally (detmath's Erf and Erfinv call detmath's Exp and Log instead).

Each function here is a line-for-line port of the PURE-GO path of the Go 1.26 source named in its doc comment, with one change: every floating-point product is wrapped in an explicit float64(...) conversion. The Go spec allows `a + b*c` to be contracted into a single fused multiply-add, possibly across statements, and an explicit conversion is the only construct that forbids it. With every product barriered the compiler emits the same sequence of individually rounded IEEE-754 operations on every architecture, so a result's bits are a function of the input alone. math.FMA is the wrong lever: it forces fusion everywhere, which is portable but changes amd64's values.

math.Sqrt, math.Abs, math.Copysign, math.Frexp, math.Ldexp, math.Modf and the classification helpers are called directly: IEEE-754 requires Sqrt to be correctly rounded, and the rest are exact bit manipulation (Ldexp's subnormal scaling is a single multiply that feeds no add).

Every special case (NaN, ±Inf, ±0, subnormals, overflow, underflow) follows math.*'s documented semantics. On linux/amd64, where the compiler never contracts, Expm1 / Log1p / Sin / Cos / Tan / Atan2 / Asin are bit-for-bit math.*; elsewhere results sit within the stdlib's own cross-build spread: 1 ulp, 2 for Exp (amd64's FMA assembly) and Tan (arm64 contraction), 8 for Pow (it compounds Exp∘Log) and Erfinv (arm64 contracts its degree-7 Horner chains). Two amd64 assembly defects are not shared: math.Log of a subnormal returns about -709.09, and math.Exp overflows to +Inf from about 709.44 instead of 709.78. The gate is TestDetmath_GoldenBits (exact uint64 pins that must hold on amd64 and arm64 alike) plus TestDetmath_NoFMAContraction, which cross-compiles the package for arm64 with -d=fmahash=vy and fails on any contraction the compiler reports.

NormFloat64 / ExpFloat64 (ziggurat.go) port math/rand/v2's ziggurat methods: they take the caller's *rand.Rand and consume it exactly as the methods do (TestDetmath_ZigguratStreamParity / TestDetmath_ZigguratConsumption), so a seed's stream is unchanged, but their rare tail and wedge branches call Log / Exp above and never fuse a multiply-add, so a draw's bits are a function of the seed alone (TestDetmath_ZigguratGoldenBits). The float32 wedge products are barriered with float32(...), which the source scan accepts for a float32 product only.

The ported code is Copyright The Go Authors and is used under the BSD license reproduced in THIRD_PARTY_NOTICES.md at the repository root.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Asin

func Asin(x float64) float64

Asin returns the arcsine, in radians, of x, with bits that are identical on every architecture and CPU.

Port of the pure-Go asin in Go 1.26 src/math/asin.go. Special cases are the same as math.Asin:

Asin(±0) = ±0
Asin(x) = NaN if x < -1 or x > 1

func Atan2

func Atan2(y, x float64) float64

Atan2 returns the arc tangent of y/x, using the signs of the two to determine the quadrant of the return value, with bits that are identical on every architecture and CPU.

Port of the pure-Go atan2 in Go 1.26 src/math/atan2.go (over the pure-Go atan in src/math/atan.go). Special cases are the same as math.Atan2 (in order):

Atan2(y, NaN) = NaN
Atan2(NaN, x) = NaN
Atan2(+0, x>=0) = +0
Atan2(-0, x>=0) = -0
Atan2(+0, x<=-0) = +Pi
Atan2(-0, x<=-0) = -Pi
Atan2(y>0, 0) = +Pi/2
Atan2(y<0, 0) = -Pi/2
Atan2(+Inf, +Inf) = +Pi/4
Atan2(-Inf, +Inf) = -Pi/4
Atan2(+Inf, -Inf) = 3Pi/4
Atan2(-Inf, -Inf) = -3Pi/4
Atan2(y, +Inf) = 0
Atan2(y>0, -Inf) = +Pi
Atan2(y<0, -Inf) = -Pi
Atan2(+Inf, x) = +Pi/2
Atan2(-Inf, x) = -Pi/2

func Cos

func Cos(x float64) float64

Cos returns the cosine of the radian argument x, with bits that are identical on every architecture and CPU.

Port of the pure-Go cos in Go 1.26 src/math/sin.go. Special cases are the same as math.Cos:

Cos(±Inf) = NaN
Cos(NaN) = NaN

func Erf

func Erf(x float64) float64

Erf returns the error function of x, with bits that are identical on every architecture and CPU.

Port of the pure-Go erf in Go 1.26 src/math/erf.go. Special cases are the same as math.Erf:

Erf(+Inf) = 1
Erf(-Inf) = -1
Erf(NaN) = NaN

func Erfinv

func Erfinv(x float64) float64

Erfinv returns the inverse error function of x, with bits that are identical on every architecture and CPU.

Port of Go 1.26 src/math/erfinv.go. Special cases are the same as math.Erfinv:

Erfinv(1) = +Inf
Erfinv(-1) = -Inf
Erfinv(x) = NaN if x < -1 or x > 1
Erfinv(NaN) = NaN

func Exp

func Exp(x float64) float64

Exp returns e**x, the base-e exponential of x, with bits that are identical on every architecture and CPU.

Port of the pure-Go exp in Go 1.26 src/math/exp.go. Special cases are the same as math.Exp:

Exp(+Inf) = +Inf
Exp(NaN) = NaN

Very large values overflow to 0 or +Inf. Very small values underflow to 1.

func ExpFloat64

func ExpFloat64(r *rand.Rand) float64

ExpFloat64 returns an exponentially distributed float64 with rate 1 (mean 1) drawn from r, with bits that are identical on every architecture and CPU.

It is (*rand.Rand).ExpFloat64 from Go 1.26 src/math/rand/v2/exp.go line for line and consumes r exactly as it does (see NormFloat64).

func Expm1

func Expm1(x float64) float64

Expm1 returns e**x - 1, the base-e exponential of x minus 1, with bits that are identical on every architecture and CPU. It is more accurate than Exp(x) - 1 when x is near zero.

Port of the pure-Go expm1 in Go 1.26 src/math/expm1.go. Special cases are the same as math.Expm1:

Expm1(+Inf) = +Inf
Expm1(-Inf) = -1
Expm1(NaN) = NaN

Very large values overflow to -1 or +Inf.

func Log

func Log(x float64) float64

Log returns the natural logarithm of x, with bits that are identical on every architecture and CPU.

Port of the pure-Go log in Go 1.26 src/math/log.go. Special cases are the same as math.Log:

Log(+Inf) = +Inf
Log(0) = -Inf
Log(x < 0) = NaN
Log(NaN) = NaN

func Log1p

func Log1p(x float64) float64

Log1p returns the natural logarithm of 1 plus its argument x, with bits that are identical on every architecture and CPU. It is more accurate than Log(1 + x) when x is near zero.

Port of the pure-Go log1p in Go 1.26 src/math/log1p.go. Special cases are the same as math.Log1p:

Log1p(+Inf) = +Inf
Log1p(±0) = ±0
Log1p(-1) = -Inf
Log1p(x < -1) = NaN
Log1p(NaN) = NaN

func NormFloat64

func NormFloat64(r *rand.Rand) float64

NormFloat64 returns a standard normal (mean 0, stddev 1) float64 drawn from r, with bits that are identical on every architecture and CPU.

It is (*rand.Rand).NormFloat64 from Go 1.26 src/math/rand/v2/normal.go line for line, and consumes r EXACTLY as it does — the same Uint64 / Float64 calls in the same order, the same count per draw — so a seed's stream position after any number of draws is unchanged. Only the rare tail and wedge branches can produce different bits from the stdlib's: they call this package's Log / Exp instead of the machine-specific math.Log / math.Exp, and their multiply-adds are never fused.

func Pow

func Pow(x, y float64) float64

Pow returns x**y, the base-x exponential of y, with bits that are identical on every architecture and CPU.

Port of the pure-Go pow in Go 1.26 src/math/pow.go; the fractional part of y goes through this package's Exp and Log rather than math.Exp / math.Log, which are assembly on amd64 / arm64. Special cases are the same as math.Pow (in order):

Pow(x, ±0) = 1 for any x
Pow(1, y) = 1 for any y
Pow(x, 1) = x for any x
Pow(NaN, y) = NaN
Pow(x, NaN) = NaN
Pow(±0, y) = ±Inf for y an odd integer < 0
Pow(±0, -Inf) = +Inf
Pow(±0, +Inf) = +0
Pow(±0, y) = +Inf for finite y < 0 and not an odd integer
Pow(±0, y) = ±0 for y an odd integer > 0
Pow(±0, y) = +0 for finite y > 0 and not an odd integer
Pow(-1, ±Inf) = 1
Pow(x, +Inf) = +Inf for |x| > 1
Pow(x, -Inf) = +0 for |x| > 1
Pow(x, +Inf) = +0 for |x| < 1
Pow(x, -Inf) = +Inf for |x| < 1
Pow(+Inf, y) = +Inf for y > 0
Pow(+Inf, y) = +0 for y < 0
Pow(-Inf, y) = Pow(-0, -y)
Pow(x, y) = NaN for finite x < 0 and finite non-integer y

func Sin

func Sin(x float64) float64

Sin returns the sine of the radian argument x, with bits that are identical on every architecture and CPU.

Port of the pure-Go sin in Go 1.26 src/math/sin.go. Special cases are the same as math.Sin:

Sin(±0) = ±0
Sin(±Inf) = NaN
Sin(NaN) = NaN

func Tan

func Tan(x float64) float64

Tan returns the tangent of the radian argument x, with bits that are identical on every architecture and CPU.

Port of the pure-Go tan in Go 1.26 src/math/tan.go. Special cases are the same as math.Tan:

Tan(±0) = ±0
Tan(±Inf) = NaN
Tan(NaN) = NaN

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL