]> Cypherpunks repositories - gostls13.git/commitdiff
math: handle denormals in Frexp, Ilogb, Ldexp, and Logb
authorEoghan Sherry <ejsherry@gmail.com>
Wed, 19 Jan 2011 19:23:59 +0000 (14:23 -0500)
committerRuss Cox <rsc@golang.org>
Wed, 19 Jan 2011 19:23:59 +0000 (14:23 -0500)
Also:
* document special cases for Frexp and Ldexp
* handle ±Inf in Ldexp
* correctly return -0 on underflow in Ldexp
* test special cases for Ldexp
* test boundary cases for Frexp, Ilogb, Ldexp, and Logb

R=rsc
CC=golang-dev
https://golang.org/cl/3676041

src/pkg/math/all_test.go
src/pkg/math/bits.go
src/pkg/math/frexp.go
src/pkg/math/ldexp.go
src/pkg/math/logb.go

index 6033d37e32488747a7fe963af9b803eabcef9452..0efef2bbf0a8d2272f2f5c6866b22a539a54e525 100644 (file)
@@ -1112,6 +1112,33 @@ var jM3SC = []float64{
        NaN(),
 }
 
+var vfldexpSC = []fi{
+       {0, 0},
+       {0, -1075},
+       {0, 1024},
+       {Copysign(0, -1), 0},
+       {Copysign(0, -1), -1075},
+       {Copysign(0, -1), 1024},
+       {Inf(1), 0},
+       {Inf(1), -1024},
+       {Inf(-1), 0},
+       {Inf(-1), -1024},
+       {NaN(), -1024},
+}
+var ldexpSC = []float64{
+       0,
+       0,
+       0,
+       Copysign(0, -1),
+       Copysign(0, -1),
+       Copysign(0, -1),
+       Inf(1),
+       Inf(1),
+       Inf(-1),
+       Inf(-1),
+       NaN(),
+}
+
 var vflgammaSC = []float64{
        Inf(-1),
        -3,
@@ -1440,6 +1467,65 @@ var yM3SC = []float64{
        NaN(),
 }
 
+// arguments and expected results for boundary cases
+const (
+       SmallestNormalFloat64   = 2.2250738585072014e-308 // 2**-1022
+       LargestSubnormalFloat64 = SmallestNormalFloat64 - SmallestNonzeroFloat64
+)
+
+var vffrexpBC = []float64{
+       SmallestNormalFloat64,
+       LargestSubnormalFloat64,
+       SmallestNonzeroFloat64,
+       MaxFloat64,
+       -SmallestNormalFloat64,
+       -LargestSubnormalFloat64,
+       -SmallestNonzeroFloat64,
+       -MaxFloat64,
+}
+var frexpBC = []fi{
+       {0.5, -1021},
+       {0.99999999999999978, -1022},
+       {0.5, -1073},
+       {0.99999999999999989, 1024},
+       {-0.5, -1021},
+       {-0.99999999999999978, -1022},
+       {-0.5, -1073},
+       {-0.99999999999999989, 1024},
+}
+
+var vfldexpBC = []fi{
+       {SmallestNormalFloat64, -52},
+       {LargestSubnormalFloat64, -51},
+       {SmallestNonzeroFloat64, 1074},
+       {MaxFloat64, -(1023 + 1074)},
+       {1, -1075},
+       {-1, -1075},
+       {1, 1024},
+       {-1, 1024},
+}
+var ldexpBC = []float64{
+       SmallestNonzeroFloat64,
+       1e-323, // 2**-1073
+       1,
+       1e-323, // 2**-1073
+       0,
+       Copysign(0, -1),
+       Inf(1),
+       Inf(-1),
+}
+
+var logbBC = []float64{
+       -1022,
+       -1023,
+       -1074,
+       1023,
+       -1022,
+       -1023,
+       -1074,
+       1023,
+}
+
 func tolerance(a, b, e float64) bool {
        d := a - b
        if d < 0 {
@@ -1792,6 +1878,11 @@ func TestFrexp(t *testing.T) {
                        t.Errorf("Frexp(%g) = %g, %d, want %g, %d", vffrexpSC[i], f, j, frexpSC[i].f, frexpSC[i].i)
                }
        }
+       for i := 0; i < len(vffrexpBC); i++ {
+               if f, j := Frexp(vffrexpBC[i]); !alike(frexpBC[i].f, f) || frexpBC[i].i != j {
+                       t.Errorf("Frexp(%g) = %g, %d, want %g, %d", vffrexpBC[i], f, j, frexpBC[i].f, frexpBC[i].i)
+               }
+       }
 }
 
 func TestGamma(t *testing.T) {
@@ -1833,6 +1924,11 @@ func TestIlogb(t *testing.T) {
                        t.Errorf("Ilogb(%g) = %d, want %d", vflogbSC[i], e, ilogbSC[i])
                }
        }
+       for i := 0; i < len(vffrexpBC); i++ {
+               if e := Ilogb(vffrexpBC[i]); int(logbBC[i]) != e {
+                       t.Errorf("Ilogb(%g) = %d, want %d", vffrexpBC[i], e, int(logbBC[i]))
+               }
+       }
 }
 
 func TestJ0(t *testing.T) {
@@ -1891,6 +1987,21 @@ func TestLdexp(t *testing.T) {
                        t.Errorf("Ldexp(%g, %d) = %g, want %g", frexpSC[i].f, frexpSC[i].i, f, vffrexpSC[i])
                }
        }
+       for i := 0; i < len(vfldexpSC); i++ {
+               if f := Ldexp(vfldexpSC[i].f, vfldexpSC[i].i); !alike(ldexpSC[i], f) {
+                       t.Errorf("Ldexp(%g, %d) = %g, want %g", vfldexpSC[i].f, vfldexpSC[i].i, f, ldexpSC[i])
+               }
+       }
+       for i := 0; i < len(vffrexpBC); i++ {
+               if f := Ldexp(frexpBC[i].f, frexpBC[i].i); !alike(vffrexpBC[i], f) {
+                       t.Errorf("Ldexp(%g, %d) = %g, want %g", frexpBC[i].f, frexpBC[i].i, f, vffrexpBC[i])
+               }
+       }
+       for i := 0; i < len(vfldexpBC); i++ {
+               if f := Ldexp(vfldexpBC[i].f, vfldexpBC[i].i); !alike(ldexpBC[i], f) {
+                       t.Errorf("Ldexp(%g, %d) = %g, want %g", vfldexpBC[i].f, vfldexpBC[i].i, f, ldexpBC[i])
+               }
+       }
 }
 
 func TestLgamma(t *testing.T) {
@@ -1934,6 +2045,11 @@ func TestLogb(t *testing.T) {
                        t.Errorf("Logb(%g) = %g, want %g", vflogbSC[i], f, logbSC[i])
                }
        }
+       for i := 0; i < len(vffrexpBC); i++ {
+               if e := Logb(vffrexpBC[i]); !alike(logbBC[i], e) {
+                       t.Errorf("Ilogb(%g) = %g, want %g", vffrexpBC[i], e, logbBC[i])
+               }
+       }
 }
 
 func TestLog10(t *testing.T) {
index 1a97e76799ad594332e07f47796a35a00732480f..a1dca3ed695a10689f462c76b3ce56c0d7d04903 100644 (file)
@@ -47,3 +47,13 @@ func IsInf(f float64, sign int) bool {
        //      return sign >= 0 && x == uvinf || sign <= 0 && x == uvneginf;
        return sign >= 0 && f > MaxFloat64 || sign <= 0 && f < -MaxFloat64
 }
+
+// normalize returns a normal number y and exponent exp
+// satisfying x == y × 2**exp. It assumes x is finite and non-zero.
+func normalize(x float64) (y float64, exp int) {
+       const SmallestNormal = 2.2250738585072014e-308 // 2**-1022
+       if Fabs(x) < SmallestNormal {
+               return x * (1 << 52), -52
+       }
+       return x, 0
+}
index 203219c0dce6a58943f6cf50e1e94d7599e3f091..867b78f36489436dd77dd9beabf96f2cdb8bce52 100644 (file)
@@ -8,6 +8,11 @@ package math
 // and an integral power of two.
 // It returns frac and exp satisfying f == frac × 2**exp,
 // with the absolute value of frac in the interval [½, 1).
+//
+// Special cases are:
+//     Frexp(±0) = ±0, 0
+//     Frexp(±Inf) = ±Inf, 0
+//     Frexp(NaN) = NaN, 0
 func Frexp(f float64) (frac float64, exp int) {
        // TODO(rsc): Remove manual inlining of IsNaN, IsInf
        // when compiler does it for us
@@ -18,8 +23,9 @@ func Frexp(f float64) (frac float64, exp int) {
        case f < -MaxFloat64 || f > MaxFloat64 || f != f: // IsInf(f, 0) || IsNaN(f):
                return f, 0
        }
+       f, exp = normalize(f)
        x := Float64bits(f)
-       exp = int((x>>shift)&mask) - bias + 1
+       exp += int((x>>shift)&mask) - bias + 1
        x &^= mask << shift
        x |= (-1 + bias) << shift
        frac = Float64frombits(x)
index d04bf1581ad5fbb85dec98956f7435ba0c58e0c4..96c95cad4ae11fc2ff3c573aa9730bc9992a276b 100644 (file)
@@ -6,6 +6,11 @@ package math
 
 // Ldexp is the inverse of Frexp.
 // It returns frac × 2**exp.
+//
+// Special cases are:
+//     Ldexp(±0, exp) = ±0
+//     Ldexp(±Inf, exp) = ±Inf
+//     Ldexp(NaN, exp) = NaN
 func Ldexp(frac float64, exp int) float64 {
        // TODO(rsc): Remove manual inlining of IsNaN, IsInf
        // when compiler does it for us
@@ -13,21 +18,28 @@ func Ldexp(frac float64, exp int) float64 {
        switch {
        case frac == 0:
                return frac // correctly return -0
-       case frac != frac: // IsNaN(frac):
-               return NaN()
+       case frac < -MaxFloat64 || frac > MaxFloat64 || frac != frac: // IsInf(frac, 0) || IsNaN(frac):
+               return frac
        }
+       frac, e := normalize(frac)
+       exp += e
        x := Float64bits(frac)
-       exp += int(x>>shift) & mask
-       if exp <= 0 {
-               return 0 // underflow
+       exp += int(x>>shift)&mask - bias
+       if exp < -1074 {
+               return Copysign(0, frac) // underflow
        }
-       if exp >= mask { // overflow
+       if exp > 1023 { // overflow
                if frac < 0 {
                        return Inf(-1)
                }
                return Inf(1)
        }
+       var m float64 = 1
+       if exp < -1022 { // denormal
+               exp += 52
+               m = 1.0 / (1 << 52) // 2**-52
+       }
        x &^= mask << shift
-       x |= uint64(exp) << shift
-       return Float64frombits(x)
+       x |= uint64(exp+bias) << shift
+       return m * Float64frombits(x)
 }
index 9e465151711d83061bb0a9004eae184bbf428bb6..072281ddf9f852de98fbe8793575e4cefd759524 100644 (file)
@@ -4,7 +4,7 @@
 
 package math
 
-// Logb(x) returns the binary exponent of non-zero x.
+// Logb(x) returns the binary exponent of x.
 //
 // Special cases are:
 //     Logb(±Inf) = +Inf
@@ -22,10 +22,10 @@ func Logb(x float64) float64 {
        case x != x: // IsNaN(x):
                return x
        }
-       return float64(int((Float64bits(x)>>shift)&mask) - bias)
+       return float64(ilogb(x))
 }
 
-// Ilogb(x) returns the binary exponent of non-zero x as an integer.
+// Ilogb(x) returns the binary exponent of x as an integer.
 //
 // Special cases are:
 //     Ilogb(±Inf) = MaxInt32
@@ -43,5 +43,12 @@ func Ilogb(x float64) int {
        case x < -MaxFloat64 || x > MaxFloat64: // IsInf(x, 0):
                return MaxInt32
        }
-       return int((Float64bits(x)>>shift)&mask) - bias
+       return ilogb(x)
+}
+
+// logb returns the binary exponent of x. It assumes x is finite and
+// non-zero.
+func ilogb(x float64) int {
+       x, exp := normalize(x)
+       return int((Float64bits(x)>>shift)&mask) - bias + exp
 }