Skip to content

Commit a2f41eb

Browse files
authored
DS-704: Implement calculated streams support for Pow, Sqrt, Ln, Log, and Truncate. (#184)
1 parent c251fb6 commit a2f41eb

File tree

2 files changed

+401
-0
lines changed

2 files changed

+401
-0
lines changed

llo/stream_calculated.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ var (
3636
"Add": Add,
3737
"Sum": Add,
3838
"Sub": Sub,
39+
"Pow": Pow,
40+
"Sqrt": Sqrt,
41+
"Ln": Ln,
42+
"Log": Log,
3943
"IsZero": IsZero,
4044
"IsNegative": IsNegative,
4145
"IsPositive": IsPositive,
@@ -67,6 +71,10 @@ var (
6771
"Add": true,
6872
"Sum": true,
6973
"Sub": true,
74+
"Pow": true,
75+
"Sqrt": true,
76+
"Ln": true,
77+
"Log": true,
7078
"IsZero": true,
7179
"IsNegative": true,
7280
"IsPositive": true,
@@ -80,6 +88,15 @@ var (
8088
}
8189
)
8290

91+
const (
92+
// precision defines the precision level for power calculations, representing the number of decimal places.
93+
// See PowerWithPrecision at https://github.com/shopspring/decimal/blob/master/decimal.go#L798.
94+
precision = 18
95+
// doublePrecision is used when we intend to further modify the result and we don't want to suffer from rounding
96+
// errors.
97+
doublePrecision = 2 * precision
98+
)
99+
83100
type environment map[string]any
84101

85102
func (e environment) SetStreamValue(id llotypes.StreamID, value StreamValue) error {
@@ -328,6 +345,83 @@ func Sub(x, y any) (decimal.Decimal, error) {
328345
return ad.Sub(bd), nil
329346
}
330347

348+
// Pow returns x, raised to the power of y
349+
func Pow(x, y any) (decimal.Decimal, error) {
350+
base, err := toDecimal(x)
351+
if err != nil {
352+
return decimal.Decimal{}, err
353+
}
354+
power, err := toDecimal(y)
355+
if err != nil {
356+
return decimal.Decimal{}, err
357+
}
358+
// We use double precision here in order to offset any float approximation errors.
359+
res, err := base.PowWithPrecision(power, doublePrecision)
360+
if err != nil {
361+
return decimal.Decimal{}, err
362+
}
363+
return res.Round(precision), nil
364+
}
365+
366+
// Sqrt returns the square root of x. Returns error for negative values.
367+
func Sqrt(x any) (decimal.Decimal, error) {
368+
n, err := toDecimal(x)
369+
if err != nil {
370+
return decimal.Decimal{}, err
371+
}
372+
if n.IsNegative() {
373+
return decimal.Decimal{}, fmt.Errorf("negative number")
374+
}
375+
sqrtPow, _ := toDecimal(0.5)
376+
// We use double precision here in order to offset any float approximation errors.
377+
res, err := n.PowWithPrecision(sqrtPow, doublePrecision)
378+
if err != nil {
379+
return decimal.Decimal{}, err
380+
}
381+
return res.Round(precision), nil
382+
}
383+
384+
// Ln returns the natural logarithm of x.
385+
func Ln(x any) (decimal.Decimal, error) {
386+
n, err := toDecimal(x)
387+
if err != nil {
388+
return decimal.Decimal{}, err
389+
}
390+
if n.IsZero() {
391+
return decimal.Decimal{}, fmt.Errorf("cannot represent natural logarithm of 0")
392+
}
393+
return n.Ln(precision)
394+
}
395+
396+
// Log returns the logarithms of y with base x. This is equivalent to log_x(y).
397+
//
398+
// We use this formula:
399+
//
400+
// ln(y)
401+
// log_x(y) = ----
402+
// ln(x)
403+
func Log(x, y any) (decimal.Decimal, error) {
404+
log, err := toDecimal(x)
405+
if err != nil {
406+
return decimal.Decimal{}, err
407+
}
408+
lnLog, err := log.Ln(doublePrecision) // double precision, since we're going to divide them
409+
if err != nil {
410+
return decimal.Decimal{}, err
411+
}
412+
413+
base, err := toDecimal(y)
414+
if err != nil {
415+
return decimal.Decimal{}, err
416+
}
417+
lnBase, err := base.Ln(doublePrecision) // double precision, since we're going to divide them
418+
if err != nil {
419+
return decimal.Decimal{}, err
420+
}
421+
422+
return lnBase.DivRound(lnLog, precision), nil
423+
}
424+
331425
// IsZero returns true if x is zero
332426
func IsZero(x any) (bool, error) {
333427
ad, err := toDecimal(x)
@@ -367,6 +461,15 @@ func Round(x any, precision int) (decimal.Decimal, error) {
367461
return ad.Round(int32(precision)), nil
368462
}
369463

464+
// Truncate truncates off digits from the number, without rounding.
465+
func Truncate(x any, precision int32) (decimal.Decimal, error) {
466+
n, err := toDecimal(x)
467+
if err != nil {
468+
return decimal.Decimal{}, err
469+
}
470+
return n.Truncate(precision), nil
471+
}
472+
370473
// Duration parses a duration string into a time.Duration
371474
func Duration(x string) (time.Duration, error) {
372475
return time.ParseDuration(x)

0 commit comments

Comments
 (0)