From cefee3c919221a38d6eb93c8199d3d88ad5ae0b7 Mon Sep 17 00:00:00 2001 From: "Charles L. Dorian" Date: Wed, 2 Nov 2011 14:01:21 -0400 Subject: [PATCH] math: improved accuracy for Tan R=rsc CC=golang-dev https://golang.org/cl/5298087 --- src/pkg/math/all_test.go | 13 +--- src/pkg/math/tan.go | 148 ++++++++++++++++++++++++++++----------- 2 files changed, 108 insertions(+), 53 deletions(-) diff --git a/src/pkg/math/all_test.go b/src/pkg/math/all_test.go index b540b179323..c650a160369 100644 --- a/src/pkg/math/all_test.go +++ b/src/pkg/math/all_test.go @@ -7,7 +7,6 @@ package math_test import ( "fmt" . "math" - "runtime" "testing" ) @@ -2247,7 +2246,7 @@ func TestSqrt(t *testing.T) { func TestTan(t *testing.T) { for i := 0; i < len(vf); i++ { - if f := Tan(vf[i]); !close(tan[i], f) { + if f := Tan(vf[i]); !veryclose(tan[i], f) { t.Errorf("Tan(%g) = %g, want %g", vf[i], f, tan[i]) } } @@ -2257,16 +2256,6 @@ func TestTan(t *testing.T) { t.Errorf("Tan(%g) = %g, want %g", vfsinSC[i], f, sinSC[i]) } } - - // Make sure portable Tan(Pi/2) doesn't panic (it used to). - // The portable implementation returns NaN. - // Assembly implementations might not, - // because Pi/2 is not exactly representable. - if runtime.GOARCH != "386" { - if f := Tan(Pi / 2); !alike(f, NaN()) { - t.Errorf("Tan(%g) = %g, want %g", Pi/2, f, NaN()) - } - } } func TestTanh(t *testing.T) { diff --git a/src/pkg/math/tan.go b/src/pkg/math/tan.go index 6d7a60ba6b2..739ee80f76f 100644 --- a/src/pkg/math/tan.go +++ b/src/pkg/math/tan.go @@ -1,64 +1,130 @@ -// Copyright 2009 The Go Authors. All rights reserved. +// Copyright 2011 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 /* - Floating point tangent. + Floating-point tangent. */ -// Tan returns the tangent of x. -func Tan(x float64) float64 { - // Coefficients are #4285 from Hart & Cheney. (19.74D) - const ( - P0 = -.1306820264754825668269611177e+5 - P1 = .1055970901714953193602353981e+4 - P2 = -.1550685653483266376941705728e+2 - P3 = .3422554387241003435328470489e-1 - P4 = .3386638642677172096076369e-4 - Q0 = -.1663895238947119001851464661e+5 - Q1 = .4765751362916483698926655581e+4 - Q2 = -.1555033164031709966900124574e+3 - ) +// The original C code, the long comment, and the constants +// below were from http://netlib.sandia.gov/cephes/cmath/sin.c, +// available from http://www.netlib.org/cephes/cmath.tgz. +// The go code is a simplified version of the original C. +// +// tan.c +// +// Circular tangent +// +// SYNOPSIS: +// +// double x, y, tan(); +// y = tan( x ); +// +// DESCRIPTION: +// +// Returns the circular tangent of the radian argument x. +// +// Range reduction is modulo pi/4. A rational function +// x + x**3 P(x**2)/Q(x**2) +// is employed in the basic interval [0, pi/4]. +// +// ACCURACY: +// Relative error: +// arithmetic domain # trials peak rms +// DEC +-1.07e9 44000 4.1e-17 1.0e-17 +// IEEE +-1.07e9 30000 2.9e-16 8.1e-17 +// +// Partial loss of accuracy begins to occur at x = 2**30 = 1.074e9. The loss +// is not gradual, but jumps suddenly to about 1 part in 10e7. Results may +// be meaningless for x > 2**49 = 5.6e14. +// [Accuracy loss statement from sin.go comments.] +// +// Cephes Math Library Release 2.8: June, 2000 +// Copyright 1984, 1987, 1989, 1992, 2000 by Stephen L. Moshier +// +// The readme file at http://netlib.sandia.gov/cephes/ says: +// Some software in this archive may be from the book _Methods and +// Programs for Mathematical Functions_ (Prentice-Hall or Simon & Schuster +// International, 1989) or from the Cephes Mathematical Library, a +// commercial product. In either event, it is copyrighted by the author. +// What you see here may be used freely but it comes with no support or +// guarantee. +// +// The two known misprints in the book are repaired here in the +// source listings for the gamma function and the incomplete beta +// integral. +// +// Stephen L. Moshier +// moshier@na-net.ornl.gov - flag := false +// tan coefficients +var _tanP = [...]float64{ + -1.30936939181383777646E4, // 0xc0c992d8d24f3f38 + 1.15351664838587416140E6, // 0x413199eca5fc9ddd + -1.79565251976484877988E7, // 0xc1711fead3299176 +} +var _tanQ = [...]float64{ + 1.00000000000000000000E0, + 1.36812963470692954678E4, //0x40cab8a5eeb36572 + -1.32089234440210967447E6, //0xc13427bc582abc96 + 2.50083801823357915839E7, //0x4177d98fc2ead8ef + -5.38695755929454629881E7, //0xc189afe03cbe5a31 +} + +// Tan returns the tangent of x. +// +// Special conditions are: +// Tan(±0) = ±0 +// Tan(±Inf) = NaN +// Tan(NaN) = NaN +func Tan(x float64) float64 { + const ( + PI4A = 7.85398125648498535156E-1 // 0x3fe921fb40000000, Pi/4 split into three parts + PI4B = 3.77489470793079817668E-8 // 0x3e64442d00000000, + PI4C = 2.69515142907905952645E-15 // 0x3ce8469898cc5170, + M4PI = 1.273239544735162542821171882678754627704620361328125 // 4/pi + ) + // TODO(rsc): Remove manual inlining of IsNaN, IsInf + // when compiler does it for us + // special cases + switch { + case x == 0 || x != x: // x == 0 || IsNaN(): + return x // return ±0 || NaN() + case x < -MaxFloat64 || x > MaxFloat64: // IsInf(x, 0): + return NaN() + } + + // make argument positive but save the sign sign := false if x < 0 { x = -x sign = true } - x = x * (4 / Pi) /* overflow? */ - var e float64 - e, x = Modf(x) - i := int32(e) - switch i & 3 { - case 1: - x = 1 - x - flag = true + j := int64(x * M4PI) // integer part of x/(Pi/4), as integer for tests on the phase angle + y := float64(j) // integer part of x/(Pi/4), as float - case 2: - sign = !sign - flag = true - - case 3: - x = 1 - x - sign = !sign + /* map zeros and singularities to origin */ + if j&1 == 1 { + j += 1 + y += 1 } - xsq := x * x - temp := ((((P4*xsq+P3)*xsq+P2)*xsq+P1)*xsq + P0) * x - temp = temp / (((xsq+Q2)*xsq+Q1)*xsq + Q0) + z := ((x - y*PI4A) - y*PI4B) - y*PI4C + zz := z * z - if flag { - if temp == 0 { - return NaN() - } - temp = 1 / temp + if zz > 1e-14 { + y = z + z*(zz*(((_tanP[0]*zz)+_tanP[1])*zz+_tanP[2])/((((zz+_tanQ[1])*zz+_tanQ[2])*zz+_tanQ[3])*zz+_tanQ[4])) + } else { + y = z + } + if j&2 == 2 { + y = -1 / y } if sign { - temp = -temp + y = -y } - return temp + return y }