Skip to content

Commit 23c9d2a

Browse files
committed
feat: add ln and log (#12)
* add Ln and Log
1 parent 2569144 commit 23c9d2a

File tree

4 files changed

+87
-9
lines changed

4 files changed

+87
-9
lines changed

README.md

+5-4
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ n := args[0].(gal.Numberer).Number()
102102
or, for additional type safety:
103103

104104
```go
105-
if value, ok := args[0].(gal.Numberer); !ok {
105+
value, ok := args[0].(gal.Numberer)
106+
if !ok {
106107
return gal.NewUndefinedWithReasonf("NaN '%s'", args[0])
107108
}
108109
n := value.Number()
@@ -125,7 +126,7 @@ Escapes are supported:
125126

126127
## Bools
127128

128-
In additional to boolean expressions, sepcial contants `True` and `False` may be used.
129+
In addition to boolean expressions, sepcial contants `True` and `False` may be used.
129130

130131
Do not double-quote them, or they will become plain strings!
131132

@@ -143,7 +144,7 @@ Do not double-quote them, or they will become plain strings!
143144
* Go classifies bit shift operators with the higher `*`.
144145
* `&&` is synonymous of `And`.
145146
* `||` is synonymous of `Or`.
146-
* Types: String, Number, Bool
147+
* Types: String, Number, Bool, MultiValue
147148
* Associativity with parentheses: `(` and `)`
148149
* Functions:
149150
* Built-in: pi, cos, floor, sin, sqrt, trunc, and more (see `function.go`: `Eval()`)
@@ -156,7 +157,7 @@ A function is defined as a Go type: `type FunctionalValue func(...Value) Value`
156157

157158
Function names are case-insensitive.
158159

159-
A function can optionally accept one or more **space-separated** arguments, but it must return a single `Value`.
160+
A **function** can optionally accept one or more **space-separated arguments**, but it must return a single `Value`.
160161

161162
It should be noted that a `MultiValue` type is available that can hold multiple `Value` elements. A function can use `MultiValue` as its return type to effectively return multiple `Value`'s. Of course, as `MultiValue` is a `Value` type, functions can also accept it as part of their argument(s). Refer to the test `TestMultiValueFunctions`, for an example.
162163

function.go

+32
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ var builtInFunctions = map[string]FunctionalValue{
6262
"sqrt": Sqrt,
6363
"floor": Floor,
6464
"trunc": Trunc,
65+
"ln": Ln,
66+
"log": Log,
6567
}
6668

6769
// BuiltInFunction returns a built-in function body if known.
@@ -162,6 +164,36 @@ func Tan(args ...Value) Value {
162164
return NewUndefinedWithReasonf("tan(): invalid argument type '%s'", args[0].String())
163165
}
164166

167+
// Ln returns the natural logarithm of d.
168+
func Ln(args ...Value) Value {
169+
if len(args) != 2 {
170+
return NewUndefinedWithReasonf("ln() requires 2 arguments, got %d", len(args))
171+
}
172+
173+
if v, ok := args[0].(Numberer); ok {
174+
if p, ok := args[1].(Numberer); ok {
175+
return v.Number().Ln(int32(p.Number().value.IntPart()))
176+
}
177+
}
178+
179+
return NewUndefinedWithReasonf("ln(): invalid argument type '%s'", args[0].String())
180+
}
181+
182+
// Log returns the logarithm base 10 of d.
183+
func Log(args ...Value) Value {
184+
if len(args) != 2 {
185+
return NewUndefinedWithReasonf("log() requires 2 arguments, got %d", len(args))
186+
}
187+
188+
if v, ok := args[0].(Numberer); ok {
189+
if p, ok := args[1].(Numberer); ok {
190+
return v.Number().Log(int32(p.Number().value.IntPart()))
191+
}
192+
}
193+
194+
return NewUndefinedWithReasonf("log(): invalid argument type '%s'", args[0].String())
195+
}
196+
165197
// Sqrt returns the square root.
166198
func Sqrt(args ...Value) Value {
167199
if len(args) != 1 {

function_test.go

+19
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ func TestFactorial(t *testing.T) {
2828

2929
val = gal.Factorial(gal.NewNumber(10))
3030
assert.Equal(t, gal.NewNumber(3_628_800).String(), val.String())
31+
32+
val = gal.Factorial(gal.NewNumber(-10))
33+
assert.Equal(t, "undefined: Factorial: requires a positive integer, cannot accept -10", val.String())
3134
}
3235

3336
func TestCos(t *testing.T) {
@@ -54,3 +57,19 @@ func TestFloor(t *testing.T) {
5457
val := gal.Floor(gal.NewNumberFromFloat(0.0))
5558
assert.Equal(t, int64(0), gal.ToNumber(val).Int64())
5659
}
60+
61+
func TestLn(t *testing.T) {
62+
val := gal.Ln(gal.NewNumber(123456), gal.NewNumber(5))
63+
assert.Equal(t, "11.72364", val.String())
64+
65+
val = gal.Ln(gal.NewNumber(-123456), gal.NewNumber(5))
66+
assert.Equal(t, "undefined: Ln:cannot calculate natural logarithm for negative decimals", val.String())
67+
}
68+
69+
func TestLog(t *testing.T) {
70+
val := gal.Log(gal.NewNumber(123456), gal.NewNumber(5))
71+
assert.Equal(t, "5.09152", val.String())
72+
73+
val = gal.Log(gal.NewNumber(-123456), gal.NewNumber(5))
74+
assert.Equal(t, "undefined: Log:cannot calculate natural logarithm for negative decimals", val.String())
75+
}

value.go

+31-5
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,6 @@ func (s String) Add(other Value) Value {
158158

159159
func (s String) Multiply(other Value) Value {
160160
if v, ok := other.(Numberer); ok {
161-
// TODO: additionally, we could try and create a number from String / Bool to increase flexibility
162161
return String{
163162
value: strings.Repeat(s.value, int(v.Number().value.IntPart())),
164163
}
@@ -343,12 +342,12 @@ func (n Number) Cos() Number {
343342
}
344343
}
345344

346-
func (n Number) Sqrt() Number {
345+
func (n Number) Sqrt() Value {
347346
n, err := NewNumberFromString(
348347
new(big.Float).Sqrt(n.value.BigFloat()).String(),
349348
)
350349
if err != nil {
351-
panic(err) // TODO: :-/ - Maybe `Sqrt() Value` and here return Undefined{} instead??
350+
return NewUndefinedWithReasonf("Sqrt:" + err.Error())
352351
}
353352

354353
return n
@@ -360,6 +359,33 @@ func (n Number) Tan() Number {
360359
}
361360
}
362361

362+
func (n Number) Ln(precision int32) Value {
363+
res, err := n.value.Ln(precision)
364+
if err != nil {
365+
return NewUndefinedWithReasonf("Ln:" + err.Error())
366+
}
367+
368+
return Number{
369+
value: res,
370+
}
371+
}
372+
373+
func (n Number) Log(precision int32) Value {
374+
res1, err := n.value.Ln(precision)
375+
if err != nil {
376+
return NewUndefinedWithReasonf("Log:" + err.Error())
377+
}
378+
379+
res10, err := decimal.New(10, 0).Ln(precision)
380+
if err != nil {
381+
return NewUndefinedWithReasonf("Log:" + err.Error())
382+
}
383+
384+
return Number{
385+
value: res1.Div(res10).Truncate(precision),
386+
}
387+
}
388+
363389
func (n Number) Floor() Number {
364390
return Number{
365391
value: n.value.Floor(),
@@ -372,9 +398,9 @@ func (n Number) Trunc(precision int32) Number {
372398
}
373399
}
374400

375-
func (n Number) Factorial() Number {
401+
func (n Number) Factorial() Value {
376402
if !n.value.IsInteger() || n.value.IsNegative() {
377-
panic(fmt.Sprintf("invalid calculation: Factorial requires a positive integer, cannot accept %s", n.String())) // TODO :-/
403+
return NewUndefinedWithReasonf(fmt.Sprintf("Factorial: requires a positive integer, cannot accept %s", n.String()))
378404
}
379405

380406
res := decimal.NewFromInt(1)

0 commit comments

Comments
 (0)