Skip to the content.

Math

Arbitrary precision arithmetic via string-based digit math. Numbers are stored as strings of digits with a tracked sign and decimal position. All arithmetic is done digit-by-digit using bash’s native $(( )) on small chunks. No forks, no subshells, no external tools. Precision is bounded only by available memory and your patience.

Contents


Dependencies

. boop Math

Constructor

into=m Math 3.14159
into=m Math -42
into=m Math 0.001

Accepts any decimal string. Leading/trailing zeros are normalized. Negative zero becomes zero.

Global Configuration

__Math.setPrecision 50      # significant digits for inexact ops (default: 20)

Precision affects division, pi, and other operations that can produce infinite results. Exact operations (add, subtract, multiply) are not affected — they always produce the full result.

All operations truncate by default. Rounding is never applied automatically. Use Math.round or $obj.round N to round a result explicitly when you want it.

Fast Path

When both operands fit in a 64-bit integer (up to 18 significant digits), Math automatically uses bash’s native $(( )) arithmetic instead of the string-based digit engine. This is transparent — same API, same results, just faster. The fast path covers add, subtract, multiply, divide, and mod.

Static API — Class-Level Functions

The most common use case: do some math, get a value string back. No objects created, no cleanup needed.

into=v Math.add 1.5 2.3          # v="3.8"
into=v Math.subtract 10 3.5      # v="6.5"
into=v Math.multiply 2.5 4       # v="10"
into=v Math.divide 10 3          # v="3.333333333..."
into=v Math.mod 17 5             # v="2"
into=v Math.pow 2 10             # v="1024"
into=v Math.square 7             # v="49"
into=v Math.abs -42.5            # v="42.5"
into=v Math.neg 3.14             # v="-3.14"

Symbol Aliases

For when you want your bash to look like a calculator:

into=v Math.+ 1.5 2.3            # add
into=v Math.- 10 3.5             # subtract
into=v Math.x 2.5 4              # multiply
into=v Math.'*' 2.5 4            # multiply (quoted asterisk)
into=v Math./ 10 4               # divide

Math.DO — Infix Expression Evaluator

Shunting-yard algorithm converts infix to RPN tokens, then delegates to Math.RPN for evaluation. One evaluation engine under the hood — Math.DO is purely a precedence and parenthesis reordering pass.

Every token must be whitespace-separated, or packed together (the tokenizer handles both):

into=v Math.DO 1.5 + 2.3                    # 3.8
into=v Math.DO 2 + 3 x 4                    # 14 (precedence: x before +)
into=v Math.DO '(' 2 + 3 ')' x 4            # 20 (parens override)
into=v Math.DO "( 1.5 + 2.5 ) / 2"          # 2 (string mode)
into=v Math.DO "(2+3)x4"                    # 20 (packed — no spaces needed)
into=v Math.DO -5 + 8                        # 3 (negative numbers work)
into=v Math.DO "(-3)x4"                     # -12

Operators: +, -, x (or quoted *), / Precedence: x and / bind tighter than + and -. Left-to-right associativity for equal precedence.

Math.RPN — Reverse Polish Notation Evaluator

The shared evaluation engine. Operands first, then operators. No precedence rules — order is explicit in the token sequence. Math.DO feeds into this after its shunting-yard pass; calling Math.RPN directly skips the precedence reordering.

into=v Math.RPN 1.5 2.3 +                   # 3.8
into=v Math.RPN 3 4 + 2 x                   # 14
into=v Math.RPN "10 3 /"                     # 3.333...
into=v Math.RPN 5 3 - 2 x 1 +              # 5

Unlike Math.DO, Math.RPN does not support packed expressions (no spaces). "3 4+2x" won’t parse correctly — use 3 4 + 2 x. This is intentional: RPN has no precedence rules, so there’s no structural context to distinguish unary minus from subtraction in a packed string like 3-2. With whitespace, -2 is a negative number and - 2 is the subtraction operator followed by 2.

Both evaluators use internal bash arrays as stacks — no dependency on the Stack or Container class.

Instance Methods

Create an object, call methods on it. Instance methods return Math objects (chainable).

Arithmetic

Full-word names are the primary API. Short aliases (sub, mul, div) are kept for backward compatibility.

into=r $m.add $n            # m + n
into=r $m.subtract $n       # m - n       (also: $m.sub)
into=r $m.multiply $n       # m × n       (also: $m.mul)
into=r $m.divide $n         # m ÷ n       (also: $m.div)
into=r $m.mod $n            # m mod n     (fast path only)
into=r $m.pow $n            # m ^ n       (integer exponent)
into=r $m.square            # m²
into=r precision=30 $m.divide $n   # with precision override

Arguments can be Math object IDs or literal decimal strings.

mod uses the fast path (native $(( ))), so both operands must fit in 64-bit. For arbitrary-precision modular arithmetic, you’d need a strMod implementation (not yet built).

pow uses repeated squaring. Small bases stay on the fast path; large bases gracefully fall back to the string engine per iteration.

Comparisons

$m.eq $n && printf "equal\n"        # exit code 0 = true
$m.lt $n && printf "less\n"
$m.gt $n && printf "greater\n"
$m.le $n && printf "less or equal\n"
$m.ge $n && printf "greater or equal\n"
into=rc $m.cmp $n               # 0=equal, 1=greater, 2=less

Utility

into=r $m.abs                # absolute value
into=r $m.neg                # negated value
into=r $m.round 10           # round to 10 significant digits (half-up)
into=r $m.toScale 4          # force exactly 4 decimal places
into=i $m.toInt              # truncate to integer string
into=v $m.val                # decimal string: "3.14159"
into=s $m.toString           # Math(_id){ 3.14159 }
into=s $m.format "%.4f"      # printf-style formatting: "3.1416"
$m.isZero && printf "zero\n"     # boolean check

round uses round-half-up (not banker’s rounding). If the digit past the cut point is >= 5, it rounds up. Always.

toScale forces a specific number of decimal places — padding with trailing zeros if needed, truncating (with rounding carry) if the number has more decimals than requested.

format applies a printf format string to the value. Useful for fixed-width output, zero-padding, or controlling decimal display.

Pi

into=pi Math.pi              # pi to default precision (20 digits)
into=pi Math.pi 100          # pi to 100 significant digits
into=pi Math.pi 1000         # go big (bring coffee)

Uses Machin’s formula: π = 16·arctan(1/5) − 4·arctan(1/239). The arctan series expansion runs with raw digit/scale/neg triples (no object overhead) at extra working precision, then truncates to the requested digits.

Results are cached in __boop_static so repeated calls at the same or lower precision are instant.

Performance

The inner loops of series expansions (arctan, pi) use raw digit/scale/neg triples instead of Math objects, eliminating all object overhead from the hot path. Basic arithmetic on Math objects is already minimal — one resolve, one string op, one object create.

Chunked arithmetic processes 9 digits at a time (base 10^9) for add, subtract, and multiply. Division remains digit-at-a-time (inherently sequential — each step depends on the previous remainder).

The 64-bit fast path (toInt64/fromInt64) auto-selects native $(( )) for operands with ≤18 significant digits. This covers the vast majority of everyday arithmetic without touching the string engine.

Approximate timings (varies by hardware):

Operation Time
pi(10) ~2-4 sec
pi(20) ~8-15 sec
pi(50) ~40-60 sec

Internal Representation

Numbers are stored in the descriptor as three fields:

Field Description
digits String of digits, no leading zeros (except “0”)
scale Number of digits after the decimal point
neg 0 (positive/zero) or 1 (negative)

Example: 3.14 → digits=”314”, scale=2, neg=0

The raw arithmetic helpers (rawAdd, rawSub, rawMul, rawDiv, rawMulSmall, rawDivSmall) operate directly on these triples via namerefs, bypassing the object system entirely. They’re used internally by pi and arctan for performance.

Examples

. boop Math

# Static API — quick math, plain value strings
into=v Math.add 1.5 2.3
printf "%s\n" "$v"               # 3.8

into=v Math.DO "( 10 + 5 ) / 3"
printf "%s\n" "$v"               # 5

into=v Math.RPN 3 4 + 2 x
printf "%s\n" "$v"               # 14

# Powers
into=v Math.pow 2 32
printf "%s\n" "$v"               # 4294967296

# Static square loop
for x in 1 2 3 4 5; do
  into=v Math.square $x
  printf "%s² = %s\n" "$x" "$v"
done
# 1² = 1  ...  5² = 25

# Object API — when you need state or chaining
into=a Math 1
into=b Math 3
into=r precision=50 $a.divide $b
into=v $r.val
printf "%s\n" "$v"
# 0.33333333333333333333333333333333333333333333333333

# Rounding is always explicit
into=rounded $r.round 10
into=v $rounded.val
printf "%s\n" "$v"               # 0.3333333333

# Pi
into=pi Math.pi 30
into=v $pi.val
printf "pi = %s\n" "$v"
# pi = 3.14159265358979323846264338328

# Format for display
into=m Math 3.14159
into=s $m.format "%010.4f"
printf "%s\n" "$s"               # 00003.1416