// Copyright ©2018 The Gonum 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 hyperdual import ( "fmt" "math" "strings" ) // Number is a float64 precision hyperdual number. type Number struct { Real, E1mag, E2mag, E1E2mag float64 } var negZero = math.Float64frombits(1 << 63) // Format implements fmt.Formatter. func (d Number) Format(fs fmt.State, c rune) { prec, pOk := fs.Precision() if !pOk { prec = -1 } width, wOk := fs.Width() if !wOk { width = -1 } switch c { case 'v': if fs.Flag('#') { fmt.Fprintf(fs, "%T{Real:%#v, E1mag:%#v, E2mag:%#v, E1E2mag:%#v}", d, d.Real, d.E1mag, d.E2mag, d.E1E2mag) return } if fs.Flag('+') { fmt.Fprintf(fs, "{Real:%+v, E1mag:%+v, E2mag:%+v, E1E2mag:%+v}", d.Real, d.E1mag, d.E2mag, d.E1E2mag) return } c = 'g' prec = -1 fallthrough case 'e', 'E', 'f', 'F', 'g', 'G': fre := fmtString(fs, c, prec, width, false) fim := fmtString(fs, c, prec, width, true) fmt.Fprintf(fs, fmt.Sprintf("(%s%[2]sϵ₁%[2]sϵ₂%[2]sϵ₁ϵ₂)", fre, fim), d.Real, d.E1mag, d.E2mag, d.E1E2mag) default: fmt.Fprintf(fs, "%%!%c(%T=%[2]v)", c, d) return } } // This is horrible, but it's what we have. func fmtString(fs fmt.State, c rune, prec, width int, wantPlus bool) string { var b strings.Builder b.WriteByte('%') for _, f := range "0+- " { if fs.Flag(int(f)) || (f == '+' && wantPlus) { b.WriteByte(byte(f)) } } if width >= 0 { fmt.Fprint(&b, width) } if prec >= 0 { b.WriteByte('.') if prec > 0 { fmt.Fprint(&b, prec) } } b.WriteRune(c) return b.String() } // Add returns the sum of x and y. func Add(x, y Number) Number { return Number{ Real: x.Real + y.Real, E1mag: x.E1mag + y.E1mag, E2mag: x.E2mag + y.E2mag, E1E2mag: x.E1E2mag + y.E1E2mag, } } // Sub returns the difference of x and y, x-y. func Sub(x, y Number) Number { return Number{ Real: x.Real - y.Real, E1mag: x.E1mag - y.E1mag, E2mag: x.E2mag - y.E2mag, E1E2mag: x.E1E2mag - y.E1E2mag, } } // Mul returns the hyperdual product of x and y. func Mul(x, y Number) Number { return Number{ Real: x.Real * y.Real, E1mag: x.Real*y.E1mag + x.E1mag*y.Real, E2mag: x.Real*y.E2mag + x.E2mag*y.Real, E1E2mag: x.Real*y.E1E2mag + x.E1mag*y.E2mag + x.E2mag*y.E1mag + x.E1E2mag*y.Real, } } // Inv returns the hyperdual inverse of d. // // Special cases are: // // Inv(±Inf) = ±0-0ϵ₁-0ϵ₂±0ϵ₁ϵ₂ // Inv(±0) = ±Inf-Infϵ₁-Infϵ₂±Infϵ₁ϵ₂ func Inv(d Number) Number { if d.Real == 0 { return Number{ Real: 1 / d.Real, E1mag: math.Inf(-1), E2mag: math.Inf(-1), E1E2mag: 1 / d.Real, // Return a signed inf from a signed zero. } } d2 := d.Real * d.Real return Number{ Real: 1 / d.Real, E1mag: -d.E1mag / d2, E2mag: -d.E2mag / d2, E1E2mag: -d.E1E2mag/d2 + 2*d.E1mag*d.E2mag/(d2*d.Real), } } // Scale returns d scaled by f. func Scale(f float64, d Number) Number { return Number{Real: f * d.Real, E1mag: f * d.E1mag, E2mag: f * d.E2mag, E1E2mag: f * d.E1E2mag} } // Abs returns the absolute value of d. func Abs(d Number) Number { if math.Float64bits(d.Real)&(1<<63) == 0 { return d } return Scale(-1, d) }