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 ¶
- func Asin(x float64) float64
- func Atan2(y, x float64) float64
- func Cos(x float64) float64
- func Erf(x float64) float64
- func Erfinv(x float64) float64
- func Exp(x float64) float64
- func ExpFloat64(r *rand.Rand) float64
- func Expm1(x float64) float64
- func Log(x float64) float64
- func Log1p(x float64) float64
- func NormFloat64(r *rand.Rand) float64
- func Pow(x, y float64) float64
- func Sin(x float64) float64
- func Tan(x float64) float64
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Asin ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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
Types ¶
This section is empty.