mirror of
https://github.com/golang/go.git
synced 2025-05-05 15:43:04 +00:00
math: improve accuracy of Exp2
Note: * Exp2 doesn't have a special case for very small arguments * Exp2 hasn't been subject to a proper error analysis Also: * add tests for Exp2 with integer argument * always test Go versions of Exp and Exp2 R=rsc CC=Charlie Dorian, PeterGo, golang-dev https://golang.org/cl/3481041
This commit is contained in:
parent
6eee9ed507
commit
ff4e08f60d
@ -54,6 +54,7 @@ ALLGOFILES=\
|
|||||||
copysign.go\
|
copysign.go\
|
||||||
erf.go\
|
erf.go\
|
||||||
exp.go\
|
exp.go\
|
||||||
|
exp_port.go\
|
||||||
exp2.go\
|
exp2.go\
|
||||||
expm1.go\
|
expm1.go\
|
||||||
fabs.go\
|
fabs.go\
|
||||||
|
@ -1662,14 +1662,19 @@ func TestErfc(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestExp(t *testing.T) {
|
func TestExp(t *testing.T) {
|
||||||
|
testExp(t, Exp, "Exp")
|
||||||
|
testExp(t, ExpGo, "ExpGo")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testExp(t *testing.T, Exp func(float64) float64, name string) {
|
||||||
for i := 0; i < len(vf); i++ {
|
for i := 0; i < len(vf); i++ {
|
||||||
if f := Exp(vf[i]); !close(exp[i], f) {
|
if f := Exp(vf[i]); !close(exp[i], f) {
|
||||||
t.Errorf("Exp(%g) = %g, want %g", vf[i], f, exp[i])
|
t.Errorf("%s(%g) = %g, want %g", name, vf[i], f, exp[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i := 0; i < len(vfexpSC); i++ {
|
for i := 0; i < len(vfexpSC); i++ {
|
||||||
if f := Exp(vfexpSC[i]); !alike(expSC[i], f) {
|
if f := Exp(vfexpSC[i]); !alike(expSC[i], f) {
|
||||||
t.Errorf("Exp(%g) = %g, want %g", vfexpSC[i], f, expSC[i])
|
t.Errorf("%s(%g) = %g, want %g", name, vfexpSC[i], f, expSC[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1689,14 +1694,26 @@ func TestExpm1(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestExp2(t *testing.T) {
|
func TestExp2(t *testing.T) {
|
||||||
|
testExp2(t, Exp2, "Exp2")
|
||||||
|
testExp2(t, Exp2Go, "Exp2Go")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testExp2(t *testing.T, Exp2 func(float64) float64, name string) {
|
||||||
for i := 0; i < len(vf); i++ {
|
for i := 0; i < len(vf); i++ {
|
||||||
if f := Exp2(vf[i]); !close(exp2[i], f) {
|
if f := Exp2(vf[i]); !close(exp2[i], f) {
|
||||||
t.Errorf("Exp2(%g) = %g, want %g", vf[i], f, exp2[i])
|
t.Errorf("%s(%g) = %g, want %g", name, vf[i], f, exp2[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i := 0; i < len(vfexpSC); i++ {
|
for i := 0; i < len(vfexpSC); i++ {
|
||||||
if f := Exp2(vfexpSC[i]); !alike(expSC[i], f) {
|
if f := Exp2(vfexpSC[i]); !alike(expSC[i], f) {
|
||||||
t.Errorf("Exp2(%g) = %g, want %g", vfexpSC[i], f, expSC[i])
|
t.Errorf("%s(%g) = %g, want %g", name, vfexpSC[i], f, expSC[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for n := -1074; n < 1024; n++ {
|
||||||
|
f := Exp2(float64(n))
|
||||||
|
vf := Ldexp(1, n)
|
||||||
|
if f != vf {
|
||||||
|
t.Errorf("%s(%d) = %g, want %g", name, n, f, vf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2352,6 +2369,12 @@ func BenchmarkExp(b *testing.B) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkExpGo(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
ExpGo(.5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkExpm1(b *testing.B) {
|
func BenchmarkExpm1(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
Expm1(.5)
|
Expm1(.5)
|
||||||
@ -2364,6 +2387,12 @@ func BenchmarkExp2(b *testing.B) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkExp2Go(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
Exp2Go(.5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkFabs(b *testing.B) {
|
func BenchmarkFabs(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
Fabs(.5)
|
Fabs(.5)
|
||||||
|
@ -4,83 +4,6 @@
|
|||||||
|
|
||||||
package math
|
package math
|
||||||
|
|
||||||
|
|
||||||
// The original C code, the long comment, and the constants
|
|
||||||
// below are from FreeBSD's /usr/src/lib/msun/src/e_exp.c
|
|
||||||
// and came with this notice. The go code is a simplified
|
|
||||||
// version of the original C.
|
|
||||||
//
|
|
||||||
// ====================================================
|
|
||||||
// Copyright (C) 2004 by Sun Microsystems, Inc. All rights reserved.
|
|
||||||
//
|
|
||||||
// Permission to use, copy, modify, and distribute this
|
|
||||||
// software is freely granted, provided that this notice
|
|
||||||
// is preserved.
|
|
||||||
// ====================================================
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// exp(x)
|
|
||||||
// Returns the exponential of x.
|
|
||||||
//
|
|
||||||
// Method
|
|
||||||
// 1. Argument reduction:
|
|
||||||
// Reduce x to an r so that |r| <= 0.5*ln2 ~ 0.34658.
|
|
||||||
// Given x, find r and integer k such that
|
|
||||||
//
|
|
||||||
// x = k*ln2 + r, |r| <= 0.5*ln2.
|
|
||||||
//
|
|
||||||
// Here r will be represented as r = hi-lo for better
|
|
||||||
// accuracy.
|
|
||||||
//
|
|
||||||
// 2. Approximation of exp(r) by a special rational function on
|
|
||||||
// the interval [0,0.34658]:
|
|
||||||
// Write
|
|
||||||
// R(r**2) = r*(exp(r)+1)/(exp(r)-1) = 2 + r*r/6 - r**4/360 + ...
|
|
||||||
// We use a special Remes algorithm on [0,0.34658] to generate
|
|
||||||
// a polynomial of degree 5 to approximate R. The maximum error
|
|
||||||
// of this polynomial approximation is bounded by 2**-59. In
|
|
||||||
// other words,
|
|
||||||
// R(z) ~ 2.0 + P1*z + P2*z**2 + P3*z**3 + P4*z**4 + P5*z**5
|
|
||||||
// (where z=r*r, and the values of P1 to P5 are listed below)
|
|
||||||
// and
|
|
||||||
// | 5 | -59
|
|
||||||
// | 2.0+P1*z+...+P5*z - R(z) | <= 2
|
|
||||||
// | |
|
|
||||||
// The computation of exp(r) thus becomes
|
|
||||||
// 2*r
|
|
||||||
// exp(r) = 1 + -------
|
|
||||||
// R - r
|
|
||||||
// r*R1(r)
|
|
||||||
// = 1 + r + ----------- (for better accuracy)
|
|
||||||
// 2 - R1(r)
|
|
||||||
// where
|
|
||||||
// 2 4 10
|
|
||||||
// R1(r) = r - (P1*r + P2*r + ... + P5*r ).
|
|
||||||
//
|
|
||||||
// 3. Scale back to obtain exp(x):
|
|
||||||
// From step 1, we have
|
|
||||||
// exp(x) = 2**k * exp(r)
|
|
||||||
//
|
|
||||||
// Special cases:
|
|
||||||
// exp(INF) is INF, exp(NaN) is NaN;
|
|
||||||
// exp(-INF) is 0, and
|
|
||||||
// for finite argument, only exp(0)=1 is exact.
|
|
||||||
//
|
|
||||||
// Accuracy:
|
|
||||||
// according to an error analysis, the error is always less than
|
|
||||||
// 1 ulp (unit in the last place).
|
|
||||||
//
|
|
||||||
// Misc. info.
|
|
||||||
// For IEEE double
|
|
||||||
// if x > 7.09782712893383973096e+02 then exp(x) overflow
|
|
||||||
// if x < -7.45133219101941108420e+02 then exp(x) underflow
|
|
||||||
//
|
|
||||||
// Constants:
|
|
||||||
// The hexadecimal values are the intended ones for the following
|
|
||||||
// constants. The decimal values may be used, provided that the
|
|
||||||
// compiler will convert from decimal to binary accurately enough
|
|
||||||
// to produce the hexadecimal values shown.
|
|
||||||
|
|
||||||
// Exp returns e**x, the base-e exponential of x.
|
// Exp returns e**x, the base-e exponential of x.
|
||||||
//
|
//
|
||||||
// Special cases are:
|
// Special cases are:
|
||||||
@ -88,54 +11,4 @@ package math
|
|||||||
// Exp(NaN) = NaN
|
// Exp(NaN) = NaN
|
||||||
// Very large values overflow to 0 or +Inf.
|
// Very large values overflow to 0 or +Inf.
|
||||||
// Very small values underflow to 1.
|
// Very small values underflow to 1.
|
||||||
func Exp(x float64) float64 {
|
func Exp(x float64) float64 { return expGo(x) }
|
||||||
const (
|
|
||||||
Ln2Hi = 6.93147180369123816490e-01
|
|
||||||
Ln2Lo = 1.90821492927058770002e-10
|
|
||||||
Log2e = 1.44269504088896338700e+00
|
|
||||||
P1 = 1.66666666666666019037e-01 /* 0x3FC55555; 0x5555553E */
|
|
||||||
P2 = -2.77777777770155933842e-03 /* 0xBF66C16C; 0x16BEBD93 */
|
|
||||||
P3 = 6.61375632143793436117e-05 /* 0x3F11566A; 0xAF25DE2C */
|
|
||||||
P4 = -1.65339022054652515390e-06 /* 0xBEBBBD41; 0xC5D26BF1 */
|
|
||||||
P5 = 4.13813679705723846039e-08 /* 0x3E663769; 0x72BEA4D0 */
|
|
||||||
|
|
||||||
Overflow = 7.09782712893383973096e+02
|
|
||||||
Underflow = -7.45133219101941108420e+02
|
|
||||||
NearZero = 1.0 / (1 << 28) // 2**-28
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO(rsc): Remove manual inlining of IsNaN, IsInf
|
|
||||||
// when compiler does it for us
|
|
||||||
// special cases
|
|
||||||
switch {
|
|
||||||
case x != x || x > MaxFloat64: // IsNaN(x) || IsInf(x, 1):
|
|
||||||
return x
|
|
||||||
case x < -MaxFloat64: // IsInf(x, -1):
|
|
||||||
return 0
|
|
||||||
case x > Overflow:
|
|
||||||
return Inf(1)
|
|
||||||
case x < Underflow:
|
|
||||||
return 0
|
|
||||||
case -NearZero < x && x < NearZero:
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
r := hi - lo
|
|
||||||
|
|
||||||
// compute
|
|
||||||
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 Ldexp(y, k)
|
|
||||||
}
|
|
||||||
|
@ -7,4 +7,4 @@ package math
|
|||||||
// Exp2 returns 2**x, the base-2 exponential of x.
|
// Exp2 returns 2**x, the base-2 exponential of x.
|
||||||
//
|
//
|
||||||
// Special cases are the same as Exp.
|
// Special cases are the same as Exp.
|
||||||
func Exp2(x float64) float64 { return Exp(x * Ln2) }
|
func Exp2(x float64) float64 { return exp2Go(x) }
|
||||||
|
192
src/pkg/math/exp_port.go
Normal file
192
src/pkg/math/exp_port.go
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package math
|
||||||
|
|
||||||
|
|
||||||
|
// The original C code, the long comment, and the constants
|
||||||
|
// below are from FreeBSD's /usr/src/lib/msun/src/e_exp.c
|
||||||
|
// and came with this notice. The go code is a simplified
|
||||||
|
// version of the original C.
|
||||||
|
//
|
||||||
|
// ====================================================
|
||||||
|
// Copyright (C) 2004 by Sun Microsystems, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and distribute this
|
||||||
|
// software is freely granted, provided that this notice
|
||||||
|
// is preserved.
|
||||||
|
// ====================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// exp(x)
|
||||||
|
// Returns the exponential of x.
|
||||||
|
//
|
||||||
|
// Method
|
||||||
|
// 1. Argument reduction:
|
||||||
|
// Reduce x to an r so that |r| <= 0.5*ln2 ~ 0.34658.
|
||||||
|
// Given x, find r and integer k such that
|
||||||
|
//
|
||||||
|
// x = k*ln2 + r, |r| <= 0.5*ln2.
|
||||||
|
//
|
||||||
|
// Here r will be represented as r = hi-lo for better
|
||||||
|
// accuracy.
|
||||||
|
//
|
||||||
|
// 2. Approximation of exp(r) by a special rational function on
|
||||||
|
// the interval [0,0.34658]:
|
||||||
|
// Write
|
||||||
|
// R(r**2) = r*(exp(r)+1)/(exp(r)-1) = 2 + r*r/6 - r**4/360 + ...
|
||||||
|
// We use a special Remes algorithm on [0,0.34658] to generate
|
||||||
|
// a polynomial of degree 5 to approximate R. The maximum error
|
||||||
|
// of this polynomial approximation is bounded by 2**-59. In
|
||||||
|
// other words,
|
||||||
|
// R(z) ~ 2.0 + P1*z + P2*z**2 + P3*z**3 + P4*z**4 + P5*z**5
|
||||||
|
// (where z=r*r, and the values of P1 to P5 are listed below)
|
||||||
|
// and
|
||||||
|
// | 5 | -59
|
||||||
|
// | 2.0+P1*z+...+P5*z - R(z) | <= 2
|
||||||
|
// | |
|
||||||
|
// The computation of exp(r) thus becomes
|
||||||
|
// 2*r
|
||||||
|
// exp(r) = 1 + -------
|
||||||
|
// R - r
|
||||||
|
// r*R1(r)
|
||||||
|
// = 1 + r + ----------- (for better accuracy)
|
||||||
|
// 2 - R1(r)
|
||||||
|
// where
|
||||||
|
// 2 4 10
|
||||||
|
// R1(r) = r - (P1*r + P2*r + ... + P5*r ).
|
||||||
|
//
|
||||||
|
// 3. Scale back to obtain exp(x):
|
||||||
|
// From step 1, we have
|
||||||
|
// exp(x) = 2**k * exp(r)
|
||||||
|
//
|
||||||
|
// Special cases:
|
||||||
|
// exp(INF) is INF, exp(NaN) is NaN;
|
||||||
|
// exp(-INF) is 0, and
|
||||||
|
// for finite argument, only exp(0)=1 is exact.
|
||||||
|
//
|
||||||
|
// Accuracy:
|
||||||
|
// according to an error analysis, the error is always less than
|
||||||
|
// 1 ulp (unit in the last place).
|
||||||
|
//
|
||||||
|
// Misc. info.
|
||||||
|
// For IEEE double
|
||||||
|
// if x > 7.09782712893383973096e+02 then exp(x) overflow
|
||||||
|
// if x < -7.45133219101941108420e+02 then exp(x) underflow
|
||||||
|
//
|
||||||
|
// Constants:
|
||||||
|
// The hexadecimal values are the intended ones for the following
|
||||||
|
// constants. The decimal values may be used, provided that the
|
||||||
|
// compiler will convert from decimal to binary accurately enough
|
||||||
|
// to produce the hexadecimal values shown.
|
||||||
|
|
||||||
|
// Exp returns e**x, the base-e exponential of x.
|
||||||
|
//
|
||||||
|
// Special cases are:
|
||||||
|
// Exp(+Inf) = +Inf
|
||||||
|
// Exp(NaN) = NaN
|
||||||
|
// Very large values overflow to 0 or +Inf.
|
||||||
|
// Very small values underflow to 1.
|
||||||
|
func expGo(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
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO(rsc): Remove manual inlining of IsNaN, IsInf
|
||||||
|
// when compiler does it for us
|
||||||
|
// special cases
|
||||||
|
switch {
|
||||||
|
case x != x || x > MaxFloat64: // IsNaN(x) || IsInf(x, 1):
|
||||||
|
return x
|
||||||
|
case x < -MaxFloat64: // IsInf(x, -1):
|
||||||
|
return 0
|
||||||
|
case x > Overflow:
|
||||||
|
return 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 exp(hi, lo, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exp2 returns 2**x, the base-2 exponential of x.
|
||||||
|
//
|
||||||
|
// Special cases are the same as Exp.
|
||||||
|
func exp2Go(x float64) float64 {
|
||||||
|
const (
|
||||||
|
Ln2Hi = 6.93147180369123816490e-01
|
||||||
|
Ln2Lo = 1.90821492927058770002e-10
|
||||||
|
|
||||||
|
Overflow = 1.0239999999999999e+03
|
||||||
|
Underflow = -1.0740e+03
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: remove manual inlining of IsNaN and IsInf
|
||||||
|
// when compiler does it for us
|
||||||
|
// special cases
|
||||||
|
switch {
|
||||||
|
case x != x || x > MaxFloat64: // IsNaN(x) || IsInf(x, 1):
|
||||||
|
return x
|
||||||
|
case x < -MaxFloat64: // IsInf(x, -1):
|
||||||
|
return 0
|
||||||
|
case x > Overflow:
|
||||||
|
return Inf(1)
|
||||||
|
case x < Underflow:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// argument reduction; x = r×lg(e) + k with |r| ≤ ln(2)/2.
|
||||||
|
// computed as r = hi - lo for extra precision.
|
||||||
|
var k int
|
||||||
|
switch {
|
||||||
|
case x > 0:
|
||||||
|
k = int(x + 0.5)
|
||||||
|
case x < 0:
|
||||||
|
k = int(x - 0.5)
|
||||||
|
}
|
||||||
|
t := x - float64(k)
|
||||||
|
hi := t * Ln2Hi
|
||||||
|
lo := -t * Ln2Lo
|
||||||
|
|
||||||
|
// compute
|
||||||
|
return exp(hi, lo, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
// exp returns e**r × 2**k where r = hi - lo and |r| ≤ ln(2)/2.
|
||||||
|
func exp(hi, lo float64, k int) float64 {
|
||||||
|
const (
|
||||||
|
P1 = 1.66666666666666019037e-01 /* 0x3FC55555; 0x5555553E */
|
||||||
|
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 Ldexp(y, k)
|
||||||
|
}
|
10
src/pkg/math/exp_test.go
Normal file
10
src/pkg/math/exp_test.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package math
|
||||||
|
|
||||||
|
// Make expGo and exp2Go available for testing.
|
||||||
|
|
||||||
|
func ExpGo(x float64) float64 { return expGo(x) }
|
||||||
|
func Exp2Go(x float64) float64 { return exp2Go(x) }
|
Loading…
x
Reference in New Issue
Block a user