]> Cypherpunks repositories - gostls13.git/commitdiff
math: improved accuracy for Tan
authorCharles L. Dorian <cldorian@gmail.com>
Wed, 2 Nov 2011 18:01:21 +0000 (14:01 -0400)
committerRuss Cox <rsc@golang.org>
Wed, 2 Nov 2011 18:01:21 +0000 (14:01 -0400)
R=rsc
CC=golang-dev
https://golang.org/cl/5298087

src/pkg/math/all_test.go
src/pkg/math/tan.go

index b540b179323d67851ddb02a455c32ac663376d79..c650a160369c3e9379ec1a879b5f555984810a58 100644 (file)
@@ -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) {
index 6d7a60ba6b26e7d042e12cdd1d7606c1798e4538..739ee80f76f986e7ebf5f3605203ccfa07242419 100644 (file)
-// 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.
 */
 
+// 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
+
+// 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 {
-       // 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
+               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()
+       }
 
-       flag := false
+       // 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
 
-       case 2:
-               sign = !sign
-               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 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
 }