github.com/jingcheng-WU/gonum@v0.9.1-0.20210323123734-f1a2a11a8f7b/num/hyperdual/hyperdual_test.go (about)

     1  // Copyright ©2018 The Gonum Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package hyperdual
     6  
     7  import (
     8  	"fmt"
     9  	"math"
    10  	"testing"
    11  
    12  	"github.com/jingcheng-WU/gonum/floats/scalar"
    13  )
    14  
    15  var formatTests = []struct {
    16  	h      Number
    17  	format string
    18  	want   string
    19  }{
    20  	{h: Number{1.1, 2.1, 3.1, 4.1}, format: "%#v", want: "hyperdual.Number{Real:1.1, E1mag:2.1, E2mag:3.1, E1E2mag:4.1}"},         // Bootstrap test.
    21  	{h: Number{-1.1, -2.1, -3.1, -4.1}, format: "%#v", want: "hyperdual.Number{Real:-1.1, E1mag:-2.1, E2mag:-3.1, E1E2mag:-4.1}"}, // Bootstrap test.
    22  	{h: Number{1.1, 2.1, 3.1, 4.1}, format: "%+v", want: "{Real:1.1, E1mag:2.1, E2mag:3.1, E1E2mag:4.1}"},
    23  	{h: Number{-1.1, -2.1, -3.1, -4.1}, format: "%+v", want: "{Real:-1.1, E1mag:-2.1, E2mag:-3.1, E1E2mag:-4.1}"},
    24  	{h: Number{1, 2, 3, 4}, format: "%v", want: "(1+2ϵ₁+3ϵ₂+4ϵ₁ϵ₂)"},
    25  	{h: Number{-1, -2, -3, -4}, format: "%v", want: "(-1-2ϵ₁-3ϵ₂-4ϵ₁ϵ₂)"},
    26  	{h: Number{1, 2, 3, 4}, format: "%g", want: "(1+2ϵ₁+3ϵ₂+4ϵ₁ϵ₂)"},
    27  	{h: Number{-1, -2, -3, -4}, format: "%g", want: "(-1-2ϵ₁-3ϵ₂-4ϵ₁ϵ₂)"},
    28  	{h: Number{1, 2, 3, 4}, format: "%e", want: "(1.000000e+00+2.000000e+00ϵ₁+3.000000e+00ϵ₂+4.000000e+00ϵ₁ϵ₂)"},
    29  	{h: Number{-1, -2, -3, -4}, format: "%e", want: "(-1.000000e+00-2.000000e+00ϵ₁-3.000000e+00ϵ₂-4.000000e+00ϵ₁ϵ₂)"},
    30  	{h: Number{1, 2, 3, 4}, format: "%E", want: "(1.000000E+00+2.000000E+00ϵ₁+3.000000E+00ϵ₂+4.000000E+00ϵ₁ϵ₂)"},
    31  	{h: Number{-1, -2, -3, -4}, format: "%E", want: "(-1.000000E+00-2.000000E+00ϵ₁-3.000000E+00ϵ₂-4.000000E+00ϵ₁ϵ₂)"},
    32  	{h: Number{1, 2, 3, 4}, format: "%f", want: "(1.000000+2.000000ϵ₁+3.000000ϵ₂+4.000000ϵ₁ϵ₂)"},
    33  	{h: Number{-1, -2, -3, -4}, format: "%f", want: "(-1.000000-2.000000ϵ₁-3.000000ϵ₂-4.000000ϵ₁ϵ₂)"},
    34  }
    35  
    36  func TestFormat(t *testing.T) {
    37  	t.Parallel()
    38  	for _, test := range formatTests {
    39  		got := fmt.Sprintf(test.format, test.h)
    40  		if got != test.want {
    41  			t.Errorf("unexpected result for fmt.Sprintf(%q, %#v): got:%q, want:%q", test.format, test.h, got, test.want)
    42  		}
    43  	}
    44  }
    45  
    46  // First derivatives:
    47  
    48  func dSin(x float64) float64  { return math.Cos(x) }
    49  func dCos(x float64) float64  { return -math.Sin(x) }
    50  func dTan(x float64) float64  { return sec(x) * sec(x) }
    51  func dAsin(x float64) float64 { return 1 / math.Sqrt(1-x*x) }
    52  func dAcos(x float64) float64 { return -1 / math.Sqrt(1-x*x) }
    53  func dAtan(x float64) float64 { return 1 / (1 + x*x) }
    54  
    55  func dSinh(x float64) float64  { return math.Cosh(x) }
    56  func dCosh(x float64) float64  { return math.Sinh(x) }
    57  func dTanh(x float64) float64  { return sech(x) * sech(x) }
    58  func dAsinh(x float64) float64 { return 1 / math.Sqrt(x*x+1) }
    59  func dAcosh(x float64) float64 { return 1 / (math.Sqrt(x-1) * math.Sqrt(x+1)) }
    60  func dAtanh(x float64) float64 {
    61  	switch {
    62  	case math.Abs(x) == 1:
    63  		return math.NaN()
    64  	case math.IsInf(x, 0):
    65  		return negZero
    66  	}
    67  	return 1 / (1 - x*x)
    68  }
    69  
    70  func dExp(x float64) float64 { return math.Exp(x) }
    71  func dLog(x float64) float64 {
    72  	if x < 0 {
    73  		return math.NaN()
    74  	}
    75  	return 1 / x
    76  }
    77  func dSqrt(x float64) float64 {
    78  	// For whatever reason, math.Sqrt(-0) returns -0.
    79  	// In this case, that is clearly a wrong approach.
    80  	if x == 0 {
    81  		return math.Inf(1)
    82  	}
    83  	return 0.5 / math.Sqrt(x)
    84  }
    85  func dInv(x float64) float64 { return -1 / (x * x) }
    86  
    87  // Second derivatives:
    88  
    89  func d2Sin(x float64) float64  { return -math.Sin(x) }
    90  func d2Cos(x float64) float64  { return -math.Cos(x) }
    91  func d2Tan(x float64) float64  { return 2 * math.Tan(x) * sec(x) * sec(x) }
    92  func d2Asin(x float64) float64 { return x / math.Pow(1-x*x, 1.5) }
    93  func d2Acos(x float64) float64 { return -x / math.Pow(1-x*x, 1.5) }
    94  func d2Atan(x float64) float64 { return -2 * x / ((x*x + 1) * (x*x + 1)) }
    95  
    96  func d2Sinh(x float64) float64  { return math.Sinh(x) }
    97  func d2Cosh(x float64) float64  { return math.Cosh(x) }
    98  func d2Tanh(x float64) float64  { return -2 * math.Tanh(x) * sech(x) * sech(x) }
    99  func d2Asinh(x float64) float64 { return -x / math.Pow((x*x+1), 1.5) }
   100  func d2Acosh(x float64) float64 { return -x / (math.Pow(x-1, 1.5) * math.Pow(x+1, 1.5)) }
   101  func d2Atanh(x float64) float64 { return 2 * x / ((1 - x*x) * (1 - x*x)) }
   102  
   103  func d2Exp(x float64) float64 { return math.Exp(x) }
   104  func d2Log(x float64) float64 {
   105  	if x < 0 {
   106  		return math.NaN()
   107  	}
   108  	return -1 / (x * x)
   109  }
   110  func d2Sqrt(x float64) float64 {
   111  	// Again math.Sqyu, and math.Pow are odd.
   112  	switch x {
   113  	case math.Inf(1):
   114  		return 0
   115  	case math.Inf(-1):
   116  		return math.NaN()
   117  	}
   118  	return -0.25 * math.Pow(x, -1.5)
   119  }
   120  func d2Inv(x float64) float64 { return 2 / (x * x * x) }
   121  
   122  // Helpers:
   123  
   124  func sec(x float64) float64  { return 1 / math.Cos(x) }
   125  func sech(x float64) float64 { return 1 / math.Cosh(x) }
   126  
   127  var hyperdualTests = []struct {
   128  	name        string
   129  	x           []float64
   130  	fnHyperdual func(x Number) Number
   131  	fn          func(x float64) float64
   132  	dFn         func(x float64) float64
   133  	d2Fn        func(x float64) float64
   134  }{
   135  	{
   136  		name:        "sin",
   137  		x:           []float64{math.NaN(), math.Inf(-1), -3, -2, -1, -0.5, negZero, 0, 0.5, 1, 2, 3, math.Inf(1)},
   138  		fnHyperdual: Sin,
   139  		fn:          math.Sin,
   140  		dFn:         dSin,
   141  		d2Fn:        d2Sin,
   142  	},
   143  	{
   144  		name:        "cos",
   145  		x:           []float64{math.NaN(), math.Inf(-1), -3, -2, -1, -0.5, negZero, 0, 0.5, 1, 2, 3, math.Inf(1)},
   146  		fnHyperdual: Cos,
   147  		fn:          math.Cos,
   148  		dFn:         dCos,
   149  		d2Fn:        d2Cos,
   150  	},
   151  	{
   152  		name:        "tan",
   153  		x:           []float64{math.NaN(), math.Inf(-1), -3, -2, -1, -0.5, negZero, 0, 0.5, 1, 2, 3, math.Inf(1)},
   154  		fnHyperdual: Tan,
   155  		fn:          math.Tan,
   156  		dFn:         dTan,
   157  		d2Fn:        d2Tan,
   158  	},
   159  	{
   160  		name:        "sinh",
   161  		x:           []float64{math.NaN(), math.Inf(-1), -3, -2, -1, -0.5, negZero, 0, 0.5, 1, 2, 3, math.Inf(1)},
   162  		fnHyperdual: Sinh,
   163  		fn:          math.Sinh,
   164  		dFn:         dSinh,
   165  		d2Fn:        d2Sinh,
   166  	},
   167  	{
   168  		name:        "cosh",
   169  		x:           []float64{math.NaN(), math.Inf(-1), -3, -2, -1, -0.5, negZero, 0, 0.5, 1, 2, 3, math.Inf(1)},
   170  		fnHyperdual: Cosh,
   171  		fn:          math.Cosh,
   172  		dFn:         dCosh,
   173  		d2Fn:        d2Cosh,
   174  	},
   175  	{
   176  		name:        "tanh",
   177  		x:           []float64{math.NaN(), math.Inf(-1), -3, -2, -1, -0.5, negZero, 0, 0.5, 1, 2, 3, math.Inf(1)},
   178  		fnHyperdual: Tanh,
   179  		fn:          math.Tanh,
   180  		dFn:         dTanh,
   181  		d2Fn:        d2Tanh,
   182  	},
   183  
   184  	{
   185  		name:        "asin",
   186  		x:           []float64{math.NaN(), math.Inf(-1), -3, -2, -1, -0.5, negZero, 0, 0.5, 1, 2, 3, math.Inf(1)},
   187  		fnHyperdual: Asin,
   188  		fn:          math.Asin,
   189  		dFn:         dAsin,
   190  		d2Fn:        d2Asin,
   191  	},
   192  	{
   193  		name:        "acos",
   194  		x:           []float64{math.NaN(), math.Inf(-1), -3, -2, -1, -0.5, negZero, 0, 0.5, 1, 2, 3, math.Inf(1)},
   195  		fnHyperdual: Acos,
   196  		fn:          math.Acos,
   197  		dFn:         dAcos,
   198  		d2Fn:        d2Acos,
   199  	},
   200  	{
   201  		name:        "atan",
   202  		x:           []float64{math.NaN(), math.Inf(-1), -3, -2, -1, -0.5, negZero, 0, 0.5, 1, 2, 3, math.Inf(1)},
   203  		fnHyperdual: Atan,
   204  		fn:          math.Atan,
   205  		dFn:         dAtan,
   206  		d2Fn:        d2Atan,
   207  	},
   208  	{
   209  		name:        "asinh",
   210  		x:           []float64{math.NaN(), math.Inf(-1), -3, -2, -1, -0.5, negZero, 0, 0.5, 1, 2, 3, math.Inf(1)},
   211  		fnHyperdual: Asinh,
   212  		fn:          math.Asinh,
   213  		dFn:         dAsinh,
   214  		d2Fn:        d2Asinh,
   215  	},
   216  	{
   217  		name:        "acosh",
   218  		x:           []float64{math.NaN(), math.Inf(-1), -3, -2, -1, -0.5, negZero, 0, 0.5, 1, 2, 3, math.Inf(1)},
   219  		fnHyperdual: Acosh,
   220  		fn:          math.Acosh,
   221  		dFn:         dAcosh,
   222  		d2Fn:        d2Acosh,
   223  	},
   224  	{
   225  		name:        "atanh",
   226  		x:           []float64{math.NaN(), math.Inf(-1), -3, -2, -1, -0.5, negZero, 0, 0.5, 1, 2, 3, math.Inf(1)},
   227  		fnHyperdual: Atanh,
   228  		fn:          math.Atanh,
   229  		dFn:         dAtanh,
   230  		d2Fn:        d2Atanh,
   231  	},
   232  
   233  	{
   234  		name:        "exp",
   235  		x:           []float64{math.NaN(), math.Inf(-1), -3, -2, -1, -0.5, negZero, 0, 0.5, 1, 2, 3, math.Inf(1)},
   236  		fnHyperdual: Exp,
   237  		fn:          math.Exp,
   238  		dFn:         dExp,
   239  		d2Fn:        d2Exp,
   240  	},
   241  	{
   242  		name:        "log",
   243  		x:           []float64{math.NaN(), math.Inf(-1), -3, -2, -1, -0.5, negZero, 0, 0.5, 1, 2, 3, math.Inf(1)},
   244  		fnHyperdual: Log,
   245  		fn:          math.Log,
   246  		dFn:         dLog,
   247  		d2Fn:        d2Log,
   248  	},
   249  	{
   250  		name:        "inv",
   251  		x:           []float64{math.NaN(), math.Inf(-1), -3, -2, -1, -0.5, negZero, 0, 0.5, 1, 2, 3, math.Inf(1)},
   252  		fnHyperdual: Inv,
   253  		fn:          func(x float64) float64 { return 1 / x },
   254  		dFn:         dInv,
   255  		d2Fn:        d2Inv,
   256  	},
   257  	{
   258  		name:        "sqrt",
   259  		x:           []float64{math.NaN(), math.Inf(-1), -3, -2, -1, -0.5, negZero, 0, 0.5, 1, 2, 3, math.Inf(1)},
   260  		fnHyperdual: Sqrt,
   261  		fn:          math.Sqrt,
   262  		dFn:         dSqrt,
   263  		d2Fn:        d2Sqrt,
   264  	},
   265  
   266  	{
   267  		name: "Fike example fn",
   268  		x:    []float64{1, 2, 3, 4, 5},
   269  		fnHyperdual: func(x Number) Number {
   270  			return Mul(
   271  				Exp(x),
   272  				Inv(Sqrt(
   273  					Add(
   274  						PowReal(Sin(x), 3),
   275  						PowReal(Cos(x), 3)))))
   276  		},
   277  		fn: func(x float64) float64 {
   278  			return math.Exp(x) / math.Sqrt(math.Pow(math.Sin(x), 3)+math.Pow(math.Cos(x), 3))
   279  		},
   280  		dFn: func(x float64) float64 {
   281  			return math.Exp(x) * (3*math.Cos(x) + 5*math.Cos(3*x) + 9*math.Sin(x) + math.Sin(3*x)) /
   282  				(8 * math.Pow(math.Pow(math.Sin(x), 3)+math.Pow(math.Cos(x), 3), 1.5))
   283  		},
   284  		d2Fn: func(x float64) float64 {
   285  			return math.Exp(x) * (130 - 12*math.Cos(2*x) + 30*math.Cos(4*x) + 12*math.Cos(6*x) - 111*math.Sin(2*x) + 48*math.Sin(4*x) + 5*math.Sin(6*x)) /
   286  				(64 * math.Pow(math.Pow(math.Sin(x), 3)+math.Pow(math.Cos(x), 3), 2.5))
   287  		},
   288  	},
   289  }
   290  
   291  func TestHyperdual(t *testing.T) {
   292  	t.Parallel()
   293  	const tol = 1e-14
   294  	for _, test := range hyperdualTests {
   295  		for _, x := range test.x {
   296  			fxHyperdual := test.fnHyperdual(Number{Real: x, E1mag: 1, E2mag: 1})
   297  			fx := test.fn(x)
   298  			dFx := test.dFn(x)
   299  			d2Fx := test.d2Fn(x)
   300  			if !same(fxHyperdual.Real, fx, tol) {
   301  				t.Errorf("unexpected %s(%v): got:%v want:%v", test.name, x, fxHyperdual.Real, fx)
   302  			}
   303  			if !same(fxHyperdual.E1mag, dFx, tol) {
   304  				t.Errorf("unexpected %s'(%v) (ϵ₁): got:%v want:%v", test.name, x, fxHyperdual.E1mag, dFx)
   305  			}
   306  			if !same(fxHyperdual.E1mag, fxHyperdual.E2mag, tol) {
   307  				t.Errorf("mismatched ϵ₁ and ϵ₂ for %s(%v): ϵ₁:%v ϵ₂:%v", test.name, x, fxHyperdual.E1mag, fxHyperdual.E2mag)
   308  			}
   309  			if !same(fxHyperdual.E1E2mag, d2Fx, tol) {
   310  				t.Errorf("unexpected %s''(%v): got:%v want:%v", test.name, x, fxHyperdual.E1E2mag, d2Fx)
   311  			}
   312  		}
   313  	}
   314  }
   315  
   316  var powRealTests = []struct {
   317  	d    Number
   318  	p    float64
   319  	want Number
   320  }{
   321  	// PowReal(NaN+xϵ₁+yϵ₂, ±0) = 1+NaNϵ₁+NaNϵ₂+NaNϵ₁ϵ₂ for any x and y
   322  	{d: Number{Real: math.NaN(), E1mag: 0, E2mag: 0}, p: 0, want: Number{Real: 1, E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   323  	{d: Number{Real: math.NaN(), E1mag: 0, E2mag: 0}, p: negZero, want: Number{Real: 1, E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   324  	{d: Number{Real: math.NaN(), E1mag: 1, E2mag: 1}, p: 0, want: Number{Real: 1, E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   325  	{d: Number{Real: math.NaN(), E1mag: 2, E2mag: 2}, p: negZero, want: Number{Real: 1, E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   326  	{d: Number{Real: math.NaN(), E1mag: 3, E2mag: 3}, p: 0, want: Number{Real: 1, E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   327  	{d: Number{Real: math.NaN(), E1mag: 1, E2mag: 1}, p: negZero, want: Number{Real: 1, E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   328  	{d: Number{Real: math.NaN(), E1mag: 2, E2mag: 2}, p: 0, want: Number{Real: 1, E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   329  	{d: Number{Real: math.NaN(), E1mag: 3, E2mag: 3}, p: negZero, want: Number{Real: 1, E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   330  	{d: Number{Real: math.NaN(), E1mag: 2, E2mag: 3}, p: 0, want: Number{Real: 1, E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   331  	{d: Number{Real: math.NaN(), E1mag: 2, E2mag: 3}, p: negZero, want: Number{Real: 1, E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   332  
   333  	// PowReal(x, ±0) = 1 for any x
   334  	{d: Number{Real: 0, E1mag: 0, E2mag: 0}, p: 0, want: Number{Real: 1, E1mag: 0, E2mag: 0}},
   335  	{d: Number{Real: math.Inf(1), E1mag: 0, E2mag: 0}, p: 0, want: Number{Real: 1, E1mag: 0, E2mag: 0}},
   336  	{d: Number{Real: math.Inf(-1), E1mag: 0, E2mag: 0}, p: negZero, want: Number{Real: 1, E1mag: 0, E2mag: 0}},
   337  	{d: Number{Real: 0, E1mag: 1, E2mag: 1}, p: 0, want: Number{Real: 1, E1mag: 0, E2mag: 0}},
   338  	{d: Number{Real: math.Inf(1), E1mag: 1, E2mag: 1}, p: 0, want: Number{Real: 1, E1mag: 0, E2mag: 0}},
   339  	{d: Number{Real: math.Inf(-1), E1mag: 1, E2mag: 1}, p: negZero, want: Number{Real: 1, E1mag: 0, E2mag: 0}},
   340  	// These two satisfy the claim above, but the sign of zero is negative. Do we care?
   341  	{d: Number{Real: negZero, E1mag: 0, E2mag: 0}, p: negZero, want: Number{Real: 1, E1mag: negZero, E2mag: negZero}},
   342  	{d: Number{Real: negZero, E1mag: 1, E2mag: 1}, p: negZero, want: Number{Real: 1, E1mag: negZero, E2mag: negZero}},
   343  
   344  	// PowReal(1+xϵ₁+yϵ₂, z) = 1+xzϵ₁+yzϵ₂+2xyzϵ₁ϵ₂ for any z
   345  	{d: Number{Real: 1, E1mag: 0, E2mag: 0}, p: 0, want: Number{Real: 1, E1mag: 0, E2mag: 0, E1E2mag: 0}},
   346  	{d: Number{Real: 1, E1mag: 0, E2mag: 0}, p: 1, want: Number{Real: 1, E1mag: 0, E2mag: 0, E1E2mag: 0}},
   347  	{d: Number{Real: 1, E1mag: 0, E2mag: 0}, p: 2, want: Number{Real: 1, E1mag: 0, E2mag: 0, E1E2mag: 0}},
   348  	{d: Number{Real: 1, E1mag: 0, E2mag: 0}, p: 3, want: Number{Real: 1, E1mag: 0, E2mag: 0, E1E2mag: 0}},
   349  	{d: Number{Real: 1, E1mag: 1, E2mag: 1}, p: 0, want: Number{Real: 1, E1mag: 0, E2mag: 0, E1E2mag: 0}},
   350  	{d: Number{Real: 1, E1mag: 1, E2mag: 1}, p: 1, want: Number{Real: 1, E1mag: 1, E2mag: 1, E1E2mag: 0}},
   351  	{d: Number{Real: 1, E1mag: 1, E2mag: 1}, p: 2, want: Number{Real: 1, E1mag: 2, E2mag: 2, E1E2mag: 2}},
   352  	{d: Number{Real: 1, E1mag: 1, E2mag: 1}, p: 3, want: Number{Real: 1, E1mag: 3, E2mag: 3, E1E2mag: 6}},
   353  	{d: Number{Real: 1, E1mag: 2, E2mag: 2}, p: 0, want: Number{Real: 1, E1mag: 0, E2mag: 0, E1E2mag: 0}},
   354  	{d: Number{Real: 1, E1mag: 2, E2mag: 2}, p: 1, want: Number{Real: 1, E1mag: 2, E2mag: 2, E1E2mag: 0}},
   355  	{d: Number{Real: 1, E1mag: 2, E2mag: 2}, p: 2, want: Number{Real: 1, E1mag: 4, E2mag: 4, E1E2mag: 8}},
   356  	{d: Number{Real: 1, E1mag: 2, E2mag: 2}, p: 3, want: Number{Real: 1, E1mag: 6, E2mag: 6, E1E2mag: 24}},
   357  	{d: Number{Real: 1, E1mag: 1, E2mag: 2}, p: 0, want: Number{Real: 1, E1mag: 0, E2mag: 0, E1E2mag: 0}},
   358  	{d: Number{Real: 1, E1mag: 1, E2mag: 2}, p: 1, want: Number{Real: 1, E1mag: 1, E2mag: 2, E1E2mag: 0}},
   359  	{d: Number{Real: 1, E1mag: 1, E2mag: 2}, p: 2, want: Number{Real: 1, E1mag: 2, E2mag: 4, E1E2mag: 4}},
   360  	{d: Number{Real: 1, E1mag: 1, E2mag: 2}, p: 3, want: Number{Real: 1, E1mag: 3, E2mag: 6, E1E2mag: 12}},
   361  
   362  	// PowReal(NaN+xϵ₁+yϵ₂, 1) = NaN+xϵ₁+yϵ₂+NaNϵ₁ϵ₂ for any x
   363  	{d: Number{Real: math.NaN(), E1mag: 0, E2mag: 0}, p: 1, want: Number{Real: math.NaN(), E1mag: 0, E2mag: 0, E1E2mag: math.NaN()}},
   364  	{d: Number{Real: math.NaN(), E1mag: 1, E2mag: 1}, p: 1, want: Number{Real: math.NaN(), E1mag: 1, E2mag: 1, E1E2mag: math.NaN()}},
   365  	{d: Number{Real: math.NaN(), E1mag: 2, E2mag: 2}, p: 1, want: Number{Real: math.NaN(), E1mag: 2, E2mag: 2, E1E2mag: math.NaN()}},
   366  	{d: Number{Real: math.NaN(), E1mag: 1, E2mag: 2}, p: 1, want: Number{Real: math.NaN(), E1mag: 1, E2mag: 2, E1E2mag: math.NaN()}},
   367  
   368  	// PowReal(x, 1) = x for any x
   369  	{d: Number{Real: 0, E1mag: 0, E2mag: 0}, p: 1, want: Number{Real: 0, E1mag: 0, E2mag: 0}},
   370  	{d: Number{Real: negZero, E1mag: 0, E2mag: 0}, p: 1, want: Number{Real: negZero, E1mag: 0, E2mag: 0}},
   371  	{d: Number{Real: 0, E1mag: 1, E2mag: 1}, p: 1, want: Number{Real: 0, E1mag: 1, E2mag: 1}},
   372  	{d: Number{Real: negZero, E1mag: 1, E2mag: 1}, p: 1, want: Number{Real: negZero, E1mag: 1, E2mag: 1}},
   373  	{d: Number{Real: 0, E1mag: 1, E2mag: 2}, p: 1, want: Number{Real: 0, E1mag: 1, E2mag: 2}},
   374  	{d: Number{Real: negZero, E1mag: 1, E2mag: 2}, p: 1, want: Number{Real: negZero, E1mag: 1, E2mag: 2}},
   375  
   376  	// PowReal(NaN+xϵ₁+xϵ₂, y) = NaN+NaNϵ₁+NaNϵ₂+NaNϵ₁ϵ₂
   377  	{d: Number{Real: math.NaN(), E1mag: 0, E2mag: 0}, p: 2, want: Number{Real: math.NaN(), E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   378  	{d: Number{Real: math.NaN(), E1mag: 0, E2mag: 0}, p: 3, want: Number{Real: math.NaN(), E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   379  	{d: Number{Real: math.NaN(), E1mag: 1, E2mag: 1}, p: 2, want: Number{Real: math.NaN(), E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   380  	{d: Number{Real: math.NaN(), E1mag: 1, E2mag: 1}, p: 3, want: Number{Real: math.NaN(), E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   381  	{d: Number{Real: math.NaN(), E1mag: 2, E2mag: 2}, p: 2, want: Number{Real: math.NaN(), E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   382  	{d: Number{Real: math.NaN(), E1mag: 2, E2mag: 2}, p: 3, want: Number{Real: math.NaN(), E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   383  	{d: Number{Real: math.NaN(), E1mag: 1, E2mag: 2}, p: 2, want: Number{Real: math.NaN(), E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   384  	{d: Number{Real: math.NaN(), E1mag: 1, E2mag: 2}, p: 3, want: Number{Real: math.NaN(), E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   385  
   386  	// PowReal(x, NaN) = NaN+NaNϵ₁+NaNϵ₂+NaNϵ₁ϵ₂
   387  	{d: Number{Real: 0, E1mag: 0, E2mag: 0}, p: math.NaN(), want: Number{Real: math.NaN(), E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   388  	{d: Number{Real: 2, E1mag: 0, E2mag: 0}, p: math.NaN(), want: Number{Real: math.NaN(), E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   389  	{d: Number{Real: 3, E1mag: 0, E2mag: 0}, p: math.NaN(), want: Number{Real: math.NaN(), E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   390  	{d: Number{Real: 0, E1mag: 1, E2mag: 1}, p: math.NaN(), want: Number{Real: math.NaN(), E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   391  	{d: Number{Real: 2, E1mag: 1, E2mag: 1}, p: math.NaN(), want: Number{Real: math.NaN(), E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   392  	{d: Number{Real: 3, E1mag: 1, E2mag: 1}, p: math.NaN(), want: Number{Real: math.NaN(), E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   393  	{d: Number{Real: 0, E1mag: 2, E2mag: 2}, p: math.NaN(), want: Number{Real: math.NaN(), E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   394  	{d: Number{Real: 2, E1mag: 2, E2mag: 2}, p: math.NaN(), want: Number{Real: math.NaN(), E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   395  	{d: Number{Real: 3, E1mag: 2, E2mag: 2}, p: math.NaN(), want: Number{Real: math.NaN(), E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   396  
   397  	// Handled by math.Pow tests:
   398  	//
   399  	// Pow(±0, y) = ±Inf for y an odd integer < 0
   400  	// Pow(±0, -Inf) = +Inf
   401  	// Pow(±0, +Inf) = +0
   402  	// Pow(±0, y) = +Inf for finite y < 0 and not an odd integer
   403  	// Pow(±0, y) = ±0 for y an odd integer > 0
   404  	// Pow(±0, y) = +0 for finite y > 0 and not an odd integer
   405  	// Pow(-1, ±Inf) = 1
   406  
   407  	// PowReal(x+0ϵ₁+0ϵ₂, +Inf) = +Inf+NaNϵ₁+NaNϵ₂+NaNϵ₁ϵ₂ for |x| > 1
   408  	{d: Number{Real: 2, E1mag: 0, E2mag: 0}, p: math.Inf(1), want: Number{Real: math.Inf(1), E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   409  	{d: Number{Real: 3, E1mag: 0, E2mag: 0}, p: math.Inf(1), want: Number{Real: math.Inf(1), E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   410  
   411  	// PowReal(x+xϵ₁+yϵ₂, +Inf) = +Inf+Infϵ₁+Infϵ₂+NaNϵ₁ϵ₂ for |x| > 1
   412  	{d: Number{Real: 2, E1mag: 1, E2mag: 1}, p: math.Inf(1), want: Number{Real: math.Inf(1), E1mag: math.Inf(1), E2mag: math.Inf(1), E1E2mag: math.NaN()}},
   413  	{d: Number{Real: 3, E1mag: 1, E2mag: 1}, p: math.Inf(1), want: Number{Real: math.Inf(1), E1mag: math.Inf(1), E2mag: math.Inf(1), E1E2mag: math.NaN()}},
   414  	{d: Number{Real: 2, E1mag: 2, E2mag: 2}, p: math.Inf(1), want: Number{Real: math.Inf(1), E1mag: math.Inf(1), E2mag: math.Inf(1), E1E2mag: math.NaN()}},
   415  	{d: Number{Real: 3, E1mag: 2, E2mag: 2}, p: math.Inf(1), want: Number{Real: math.Inf(1), E1mag: math.Inf(1), E2mag: math.Inf(1), E1E2mag: math.NaN()}},
   416  	{d: Number{Real: 3, E1mag: 2, E2mag: 3}, p: math.Inf(1), want: Number{Real: math.Inf(1), E1mag: math.Inf(1), E2mag: math.Inf(1), E1E2mag: math.NaN()}},
   417  
   418  	// PowReal(x, -Inf) = +0+NaNϵ₁+NaNϵ₂+NaNϵ₁ϵ₂ for |x| > 1
   419  	{d: Number{Real: 2, E1mag: 0, E2mag: 0}, p: math.Inf(-1), want: Number{Real: 0, E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   420  	{d: Number{Real: 3, E1mag: 0, E2mag: 0}, p: math.Inf(-1), want: Number{Real: 0, E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   421  	{d: Number{Real: 2, E1mag: 1, E2mag: 1}, p: math.Inf(-1), want: Number{Real: 0, E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   422  	{d: Number{Real: 3, E1mag: 1, E2mag: 1}, p: math.Inf(-1), want: Number{Real: 0, E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   423  	{d: Number{Real: 2, E1mag: 2, E2mag: 2}, p: math.Inf(-1), want: Number{Real: 0, E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   424  	{d: Number{Real: 3, E1mag: 2, E2mag: 2}, p: math.Inf(-1), want: Number{Real: 0, E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   425  
   426  	// PowReal(x+yϵ₁+zϵ₂, +Inf) = +0+NaNϵ₁+NaNϵ₂+NaNϵ₁ϵ₂ for |x| < 1
   427  	{d: Number{Real: 0.1, E1mag: 0, E2mag: 0}, p: math.Inf(1), want: Number{Real: 0, E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   428  	{d: Number{Real: 0.1, E1mag: 0.1, E2mag: 0.1}, p: math.Inf(1), want: Number{Real: 0, E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   429  	{d: Number{Real: 0.2, E1mag: 0.2, E2mag: 0.2}, p: math.Inf(1), want: Number{Real: 0, E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   430  	{d: Number{Real: 0.5, E1mag: 0.3, E2mag: 0.5}, p: math.Inf(1), want: Number{Real: 0, E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   431  
   432  	// PowReal(x+0ϵ₁+0ϵ₂, -Inf) = +Inf+NaNϵ₁+NaNϵ₂+NaNϵ₁ϵ₂ for |x| < 1
   433  	{d: Number{Real: 0.1, E1mag: 0, E2mag: 0}, p: math.Inf(-1), want: Number{Real: math.Inf(1), E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   434  	{d: Number{Real: 0.2, E1mag: 0, E2mag: 0}, p: math.Inf(-1), want: Number{Real: math.Inf(1), E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   435  
   436  	// PowReal(x, -Inf) = +Inf-Infϵ₁-Infϵ₂+NaNϵ₁ϵ₂ for |x| < 1
   437  	{d: Number{Real: 0.1, E1mag: 0.1, E2mag: 0.1}, p: math.Inf(-1), want: Number{Real: math.Inf(1), E1mag: math.Inf(-1), E2mag: math.Inf(-1), E1E2mag: math.NaN()}},
   438  	{d: Number{Real: 0.2, E1mag: 0.1, E2mag: 0.1}, p: math.Inf(-1), want: Number{Real: math.Inf(1), E1mag: math.Inf(-1), E2mag: math.Inf(-1), E1E2mag: math.NaN()}},
   439  	{d: Number{Real: 0.1, E1mag: 0.2, E2mag: 0.2}, p: math.Inf(-1), want: Number{Real: math.Inf(1), E1mag: math.Inf(-1), E2mag: math.Inf(-1), E1E2mag: math.NaN()}},
   440  	{d: Number{Real: 0.2, E1mag: 0.3, E2mag: 0.2}, p: math.Inf(-1), want: Number{Real: math.Inf(1), E1mag: math.Inf(-1), E2mag: math.Inf(-1), E1E2mag: math.NaN()}},
   441  	{d: Number{Real: 0.1, E1mag: 1, E2mag: 1}, p: math.Inf(-1), want: Number{Real: math.Inf(1), E1mag: math.Inf(-1), E2mag: math.Inf(-1), E1E2mag: math.NaN()}},
   442  	{d: Number{Real: 0.2, E1mag: 1, E2mag: 1}, p: math.Inf(-1), want: Number{Real: math.Inf(1), E1mag: math.Inf(-1), E2mag: math.Inf(-1), E1E2mag: math.NaN()}},
   443  	{d: Number{Real: 0.1, E1mag: 2, E2mag: 2}, p: math.Inf(-1), want: Number{Real: math.Inf(1), E1mag: math.Inf(-1), E2mag: math.Inf(-1), E1E2mag: math.NaN()}},
   444  	{d: Number{Real: 0.2, E1mag: 2, E2mag: 2}, p: math.Inf(-1), want: Number{Real: math.Inf(1), E1mag: math.Inf(-1), E2mag: math.Inf(-1), E1E2mag: math.NaN()}},
   445  
   446  	// Handled by math.Pow tests:
   447  	//
   448  	// Pow(+Inf, y) = +Inf for y > 0
   449  	// Pow(+Inf, y) = +0 for y < 0
   450  	// Pow(-Inf, y) = Pow(-0, -y)
   451  
   452  	// PowReal(x, y) = NaN+NaNϵ₁+NaNϵ₂+NaNϵ₁ϵ₂ for finite x < 0 and finite non-integer y
   453  	{d: Number{Real: -1, E1mag: -1, E2mag: -1}, p: 0.5, want: Number{Real: math.NaN(), E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   454  	{d: Number{Real: -1, E1mag: 2, E2mag: 2}, p: 0.5, want: Number{Real: math.NaN(), E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   455  	{d: Number{Real: -1, E1mag: -1, E2mag: 2}, p: 0.5, want: Number{Real: math.NaN(), E1mag: math.NaN(), E2mag: math.NaN(), E1E2mag: math.NaN()}},
   456  }
   457  
   458  func TestPowReal(t *testing.T) {
   459  	t.Parallel()
   460  	const tol = 1e-15
   461  	for _, test := range powRealTests {
   462  		got := PowReal(test.d, test.p)
   463  		if !sameHyperdual(got, test.want, tol) {
   464  			t.Errorf("unexpected PowReal(%v, %v): got:%v want:%v", test.d, test.p, got, test.want)
   465  		}
   466  	}
   467  }
   468  
   469  func sameHyperdual(a, b Number, tol float64) bool {
   470  	return same(a.Real, b.Real, tol) && same(a.E1mag, b.E1mag, tol) &&
   471  		same(a.E2mag, b.E2mag, tol) && same(a.E1E2mag, b.E1E2mag, tol)
   472  }
   473  
   474  func same(a, b, tol float64) bool {
   475  	return (math.IsNaN(a) && math.IsNaN(b)) ||
   476  		(scalar.EqualWithinAbsOrRel(a, b, tol, tol) && math.Float64bits(a)&(1<<63) == math.Float64bits(b)&(1<<63))
   477  }