Commit 13c2e629 authored by Eoghan Sherry's avatar Eoghan Sherry Committed by Russ Cox

math: handle denormals in Frexp, Ilogb, Ldexp, and Logb

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
parent 01fad6a6
......@@ -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) {
......
......@@ -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
}
......@@ -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)
......
......@@ -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)
}
......@@ -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
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment