blob: 5f96c91ab8686dc1bab5e7b5687637325a64216e [file] [log] [blame]
// 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 quat
import (
"bytes"
"fmt"
"strconv"
"strings"
)
var zero Number
// Number is a float64 precision quaternion.
type Number struct {
Real, Imag, Jmag, Kmag float64
}
// Format implements fmt.Formatter.
func (q 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{%#v, %#v, %#v, %#v}", q, q.Real, q.Imag, q.Jmag, q.Kmag)
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]si%[2]sj%[2]sk)", fre, fim), q.Real, q.Imag, q.Jmag, q.Kmag)
default:
fmt.Fprintf(fs, "%%!%c(%T=%[2]v)", c, q)
return
}
}
// This is horrible, but it's what we have.
func fmtString(fs fmt.State, c rune, prec, width int, wantPlus bool) string {
// TODO(kortschak) Replace this with strings.Builder
// when go1.9 support is dropped from Gonum.
var b bytes.Buffer
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,
Imag: x.Imag + y.Imag,
Jmag: x.Jmag + y.Jmag,
Kmag: x.Kmag + y.Kmag,
}
}
// Sub returns the difference of x and y, x-y.
func Sub(x, y Number) Number {
return Number{
Real: x.Real - y.Real,
Imag: x.Imag - y.Imag,
Jmag: x.Jmag - y.Jmag,
Kmag: x.Kmag - y.Kmag,
}
}
// Mul returns the Hamiltonian product of x and y.
func Mul(x, y Number) Number {
return Number{
Real: x.Real*y.Real - x.Imag*y.Imag - x.Jmag*y.Jmag - x.Kmag*y.Kmag,
Imag: x.Real*y.Imag + x.Imag*y.Real + x.Jmag*y.Kmag - x.Kmag*y.Jmag,
Jmag: x.Real*y.Jmag - x.Imag*y.Kmag + x.Jmag*y.Real + x.Kmag*y.Imag,
Kmag: x.Real*y.Kmag + x.Imag*y.Jmag - x.Jmag*y.Imag + x.Kmag*y.Real,
}
}
// Scale returns q scaled by f.
func Scale(f float64, q Number) Number {
return Number{Real: f * q.Real, Imag: f * q.Imag, Jmag: f * q.Jmag, Kmag: f * q.Kmag}
}
// Parse converts the string s to a Number. The string may be parenthesized and
// has the format [±]N±Ni±Nj±Nk. The order of the components is not strict.
func Parse(s string) (Number, error) {
if len(s) == 0 {
return Number{}, parseError{state: -1}
}
orig := s
wantClose := s[0] == '('
if wantClose {
if s[len(s)-1] != ')' {
return Number{}, parseError{string: orig, state: -1}
}
s = s[1 : len(s)-1]
}
if len(s) == 0 {
return Number{}, parseError{string: orig, state: -1}
}
switch s[0] {
case 'n', 'N':
if strings.ToLower(s) == "nan" {
return NaN(), nil
}
case 'i', 'I':
if strings.ToLower(s) == "inf" {
return Inf(), nil
}
}
var q Number
var parts byte
for i := 0; i < 4; i++ {
beg, end, p, err := floatPart(s)
if err != nil {
return q, parseError{string: orig, state: -1}
}
if parts&(1<<p) != 0 {
return q, parseError{string: orig, state: -1}
}
parts |= 1 << p
var v float64
switch s[:end] {
case "-":
if len(s[end:]) == 0 {
return q, parseError{string: orig, state: -1}
}
v = -1
case "+":
if len(s[end:]) == 0 {
return q, parseError{string: orig, state: -1}
}
v = 1
default:
v, err = strconv.ParseFloat(s[beg:end], 64)
if err != nil {
return q, err
}
}
s = s[end:]
switch p {
case 0:
q.Real = v
case 1:
q.Imag = v
s = s[1:]
case 2:
q.Jmag = v
s = s[1:]
case 3:
q.Kmag = v
s = s[1:]
}
if len(s) == 0 {
return q, nil
}
if !isSign(rune(s[0])) {
return q, parseError{string: orig, state: -1}
}
}
return q, parseError{string: orig, state: -1}
}
func floatPart(s string) (beg, end int, part uint, err error) {
const (
wantMantSign = iota
wantMantIntInit
wantMantInt
wantMantFrac
wantExpSign
wantExpInt
wantInfN
wantInfF
wantCloseInf
wantNaNA
wantNaNN
wantCloseNaN
)
var i, state int
var r rune
for i, r = range s {
switch state {
case wantMantSign:
switch {
default:
return 0, i, 0, parseError{string: s, state: state, rune: r}
case isSign(r):
state = wantMantIntInit
case isDigit(r):
state = wantMantInt
case isDot(r):
state = wantMantFrac
case r == 'i', r == 'I':
state = wantInfN
case r == 'n', r == 'N':
state = wantNaNA
}
case wantMantIntInit:
switch {
default:
return 0, i, 0, parseError{string: s, state: state, rune: r}
case isDigit(r):
state = wantMantInt
case isDot(r):
state = wantMantFrac
case r == 'i':
// We need to sneak a look-ahead here.
if i == len(s)-1 || s[i+1] == '-' || s[i+1] == '+' {
return 0, i, 1, nil
}
fallthrough
case r == 'I':
state = wantInfN
case r == 'n', r == 'N':
state = wantNaNA
}
case wantMantInt:
switch {
default:
return 0, i, 0, parseError{string: s, state: state, rune: r}
case isDigit(r):
// Do nothing
case isDot(r):
state = wantMantFrac
case isExponent(r):
state = wantExpSign
case isSign(r):
return 0, i, 0, nil
case r == 'i':
return 0, i, 1, nil
case r == 'j':
return 0, i, 2, nil
case r == 'k':
return 0, i, 3, nil
}
case wantMantFrac:
switch {
default:
return 0, i, 0, parseError{string: s, state: state, rune: r}
case isDigit(r):
// Do nothing
case isExponent(r):
state = wantExpSign
case isSign(r):
return 0, i, 0, nil
case r == 'i':
return 0, i, 1, nil
case r == 'j':
return 0, i, 2, nil
case r == 'k':
return 0, i, 3, nil
}
case wantExpSign:
switch {
default:
return 0, i, 0, parseError{string: s, state: state, rune: r}
case isSign(r) || isDigit(r):
state = wantExpInt
}
case wantExpInt:
switch {
default:
return 0, i, 0, parseError{string: s, state: state, rune: r}
case isDigit(r):
// Do nothing
case isSign(r):
return 0, i, 0, nil
case r == 'i':
return 0, i, 1, nil
case r == 'j':
return 0, i, 2, nil
case r == 'k':
return 0, i, 3, nil
}
case wantInfN:
if r != 'n' && r != 'N' {
return 0, i, 0, parseError{string: s, state: state, rune: r}
}
state = wantInfF
case wantInfF:
if r != 'f' && r != 'F' {
return 0, i, 0, parseError{string: s, state: state, rune: r}
}
state = wantCloseInf
case wantCloseInf:
switch {
default:
return 0, i, 0, parseError{string: s, state: state, rune: r}
case isSign(r):
return 0, i, 0, nil
case r == 'i':
return 0, i, 1, nil
case r == 'j':
return 0, i, 2, nil
case r == 'k':
return 0, i, 3, nil
}
case wantNaNA:
if r != 'a' && r != 'A' {
return 0, i, 0, parseError{string: s, state: state, rune: r}
}
state = wantNaNN
case wantNaNN:
if r != 'n' && r != 'N' {
return 0, i, 0, parseError{string: s, state: state, rune: r}
}
state = wantCloseNaN
case wantCloseNaN:
if isSign(rune(s[0])) {
beg = 1
}
switch {
default:
return beg, i, 0, parseError{string: s, state: state, rune: r}
case isSign(r):
return beg, i, 0, nil
case r == 'i':
return beg, i, 1, nil
case r == 'j':
return beg, i, 2, nil
case r == 'k':
return beg, i, 3, nil
}
}
}
switch state {
case wantMantSign, wantExpSign, wantExpInt:
if state == wantExpInt && isDigit(r) {
break
}
return 0, i, 0, parseError{string: s, state: state, rune: r}
}
return 0, len(s), 0, nil
}
func isSign(r rune) bool {
return r == '+' || r == '-'
}
func isDigit(r rune) bool {
return '0' <= r && r <= '9'
}
func isExponent(r rune) bool {
return r == 'e' || r == 'E'
}
func isDot(r rune) bool {
return r == '.'
}
type parseError struct {
string string
state int
rune rune
}
func (e parseError) Error() string {
if e.state < 0 {
return fmt.Sprintf("quat: failed to parse: %q", e.string)
}
return fmt.Sprintf("quat: failed to parse in state %d with %q: %q", e.state, e.rune, e.string)
}