|
36 | 36 | "Add": Add, |
37 | 37 | "Sum": Add, |
38 | 38 | "Sub": Sub, |
| 39 | + "Pow": Pow, |
| 40 | + "Sqrt": Sqrt, |
| 41 | + "Ln": Ln, |
| 42 | + "Log": Log, |
39 | 43 | "IsZero": IsZero, |
40 | 44 | "IsNegative": IsNegative, |
41 | 45 | "IsPositive": IsPositive, |
|
67 | 71 | "Add": true, |
68 | 72 | "Sum": true, |
69 | 73 | "Sub": true, |
| 74 | + "Pow": true, |
| 75 | + "Sqrt": true, |
| 76 | + "Ln": true, |
| 77 | + "Log": true, |
70 | 78 | "IsZero": true, |
71 | 79 | "IsNegative": true, |
72 | 80 | "IsPositive": true, |
|
80 | 88 | } |
81 | 89 | ) |
82 | 90 |
|
| 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 | + |
83 | 100 | type environment map[string]any |
84 | 101 |
|
85 | 102 | func (e environment) SetStreamValue(id llotypes.StreamID, value StreamValue) error { |
@@ -328,6 +345,83 @@ func Sub(x, y any) (decimal.Decimal, error) { |
328 | 345 | return ad.Sub(bd), nil |
329 | 346 | } |
330 | 347 |
|
| 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 | + |
331 | 425 | // IsZero returns true if x is zero |
332 | 426 | func IsZero(x any) (bool, error) { |
333 | 427 | ad, err := toDecimal(x) |
@@ -367,6 +461,15 @@ func Round(x any, precision int) (decimal.Decimal, error) { |
367 | 461 | return ad.Round(int32(precision)), nil |
368 | 462 | } |
369 | 463 |
|
| 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 | + |
370 | 473 | // Duration parses a duration string into a time.Duration |
371 | 474 | func Duration(x string) (time.Duration, error) { |
372 | 475 | return time.ParseDuration(x) |
|
0 commit comments