-
Notifications
You must be signed in to change notification settings - Fork 0
/
funcs.go
189 lines (167 loc) · 4.9 KB
/
funcs.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
package expressions
import (
"errors"
"math/big"
"strconv"
"github.com/zephyrtronium/bigfloat"
)
// TODO(zeph): how represent e.g. 0F0(; ; z) = exp(z)
// Func is a function from reals to reals. Functions may but generally should
// not look up variables. The function should set r to its result and should
// not use the value of r otherwise.
type Func interface {
// Call evaluates the function. The function arguments are passed in invoc.
// semis is the indices of arguments which are preceded by semicolons.
// The function may but generally should not look up variables. The
// function must set r to its result and should not use the value of r
// otherwise. invoc has a length for which CanCall returned true. Call may
// modify the elements of invoc.
Call(ctx *Context, invoc []*big.Float, semis []int, r *big.Float) error
// CanCall returns whether the function can be called with n arguments.
// This controls how the expression parser handles instances of this
// function:
//
// 1. If a bracketed list of n > 0 expressions follows a function, the
// parser treats it as an argument list if CanCall(n). (If n is 1 and
// !CanCall(1) and CanCall(0), then the list is a multiplication;
// otherwise, it is rejected.)
//
// 2. If a bare term follows a function and CanCall(1), then the parser
// treats the term as an argument to the function. E.g., "exp x" is
// parsed as "exp(x)". (If !CanCall(1), then it is a multiplication.)
CanCall(n int) bool
}
var globalfuncs = map[string]Func{
"exp": Monadic(bigfloat.Exp),
"ln": Monadic(bigfloat.Log),
"log": logfn{},
"sqrt": Monadic((*big.Float).Sqrt),
// trig, not yet implemented in dependencies
"cos": nil,
"sin": nil,
"tan": nil,
"acos": nil,
"asin": nil,
"atan": nil,
"cosh": nil,
"sinh": nil,
"tanh": nil,
"acosh": nil,
"asinh": nil,
"atanh": nil,
// constants
"pi": Niladic(bigfloat.Pi),
"e": Niladic(func(out *big.Float) *big.Float {
var one big.Float
one.SetFloat64(1)
return bigfloat.Exp(out, &one)
}),
}
type monadic struct {
f func(out, in *big.Float) *big.Float
}
func (m monadic) Call(ctx *Context, invoc []*big.Float, semis []int, r *big.Float) (err error) {
in := invoc[0]
defer func() {
r := recover()
if r == nil {
return
}
err = r.(error) // panic if not error
if errors.As(err, new(*DomainError)) || errors.As(err, &big.ErrNaN{}) {
return
}
panic(err)
}()
r.SetPrec(ctx.Prec())
m.f(r, in)
return nil
}
func (m monadic) CanCall(n int) bool {
return n == 1
}
// Monadic wraps a function of one variable into a Func. f must set out to its
// result, to the precision of in; its return value is always ignored. If f is
// called on an argument outside f's domain, it should panic with an error of
// type big.ErrNaN, or that unwraps to it.
func Monadic(f func(out, in *big.Float) *big.Float) Func {
return monadic{f}
}
type niladic struct {
f func(out *big.Float) *big.Float
}
func (n niladic) Call(ctx *Context, invoc []*big.Float, semis []int, r *big.Float) (err error) {
r.SetPrec(ctx.Prec())
n.f(r)
return nil
}
func (n niladic) CanCall(k int) bool {
return k == 0
}
// Niladic wraps a function of zero variables, generally a function which
// computes a constant, into a Func. f must set out to its result; its return
// value is always ignored. Unlike Monadic, the wrapped function is expected
// never to panic.
func Niladic(f func(out *big.Float) *big.Float) Func {
return niladic{f}
}
// DomainError is an error returned when a function is called on arguments
// outside its domain. DomainError unwraps to big.ErrNaN.
type DomainError struct {
// X is the out-of-domain argument.
X *big.Float
// Arg is the 1-based index of the argument.
Arg int
// Func is a name identifying the function. Func may also be "/" to
// indicate an invalid division (0/0 or inf/inf), or "^" to represent an
// invalid exponentiation (base < 0).
Func string
}
func (err *DomainError) Error() string {
switch err.Func {
case "/":
if err.X.Sign() == 0 {
return "invalid division: 0/0"
}
if err.X.IsInf() {
return "invalid division: inf/inf"
}
case "^":
if err.X.Sign() < 0 {
return "invalid exponentiation: negative base " + err.X.String()
}
}
r := err.X.String() + " outside domain"
if err.Func != "" {
r += " of " + err.Func
}
if err.Arg > 0 {
r += " (argument " + strconv.Itoa(err.Arg) + ")"
}
return r
}
// logfn is the implementation of log(x) and log(x, b).
type logfn struct{}
var _ Func = logfn{}
func (logfn) Call(ctx *Context, invoc []*big.Float, semis []int, r *big.Float) error {
x := invoc[0]
if x.Signbit() {
return &DomainError{X: x, Arg: 1, Func: "log"}
}
var b *big.Float
if len(invoc) > 1 {
b = invoc[1]
if b.Signbit() {
return &DomainError{X: b, Arg: 2, Func: "log"}
}
} else {
b = big.NewFloat(10).SetPrec(r.Prec())
}
bigfloat.Log(r, x)
bigfloat.Log(x, b)
r.Quo(r, x)
return nil
}
func (logfn) CanCall(n int) bool {
return n == 1 || n == 2
}