summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-24 13:24:37 +0300
committerPaul Buetow <paul@buetow.org>2026-05-24 13:24:37 +0300
commit11ed171845b6665def6e4d06fdd2f9ea6ae631a5 (patch)
treefdb8c5ecc6f4c7eb26b4d5c79b475fd953282245 /docs
parenta9b158f0c0ce21709bbee604a32a9f4ba30822a9 (diff)
docs: add rational-mode.md documentation (task ai)
Test and document rational number mode: - rat on/off/toggle commands (REPL only) - How big.Rat integration works internally - Precision comparison examples with actual output - Known limitation: +, -, % fail for non-dyadic decimals (0.1, 0.2) due to Rat.Float64() rejecting lossy conversions - Performance trade-offs vs float64 mode - Edge cases including metrics, constants, and variables - When to use rational mode and when to stick with float mode
Diffstat (limited to 'docs')
-rw-r--r--docs/rational-mode.md322
1 files changed, 322 insertions, 0 deletions
diff --git a/docs/rational-mode.md b/docs/rational-mode.md
new file mode 100644
index 0000000..52d45b9
--- /dev/null
+++ b/docs/rational-mode.md
@@ -0,0 +1,322 @@
+# Rational Number Mode
+
+gt supports a rational number calculation mode that uses Go's `math/big.Rat` type for arbitrary-precision rational arithmetic. This mode represents numbers as exact fractions (numerator/denominator) rather than floating-point approximations.
+
+Rational mode is only available in interactive REPL mode. Start gt without arguments to enter the REPL, then use the `rat` command to switch modes.
+
+## Enabling and Disabling
+
+The `rat` command controls rational number mode. It is a REPL-only command — it cannot be used in single-command mode (`gt '...'`).
+
+### `rat on`
+
+Enable rational number mode:
+
+```
+> rat on
+Rational mode enabled
+```
+
+### `rat off`
+
+Disable rational mode and return to float64 calculations (the default):
+
+```
+> rat off
+Rational mode disabled (using float64)
+```
+
+### `rat toggle`
+
+Switch between rational mode and float64 mode:
+
+```
+> rat toggle
+Rational mode enabled
+> rat toggle
+Rational mode disabled (using float64)
+```
+
+Without an argument, `rat` prints a usage message:
+
+```
+> rat
+rat command requires an argument: on, off, or toggle
+```
+
+Invalid arguments are also reported:
+
+```
+> rat enable
+Unknown rat mode: enable. Valid modes: on, off, toggle
+```
+
+## How Rational Mode Works
+
+gt's rational mode uses Go's `math/big.Rat` type, which represents numbers as exact fractions with arbitrary-precision integer numerator and denominator.
+
+When a number is parsed in rational mode, gt calls `big.Rat.SetString()`, which creates the exact rational representation. For example:
+
+- `0.1` becomes the exact fraction `1/10`
+- `0.2` becomes the exact fraction `2/10` (stored as `1/5`)
+- `1/3` becomes `1/3` exactly
+- `3` becomes `3/1` (integer)
+
+In contrast, float64 mode stores `0.1` as the nearest representable binary fraction `3602879701896397/36028797018963968`, which is not exactly `1/10`.
+
+### Result formatting
+
+Rational numbers are displayed using `big.Rat.FloatString(10)`, which formats the value as a decimal with up to 10 significant digits. This means rational results show trailing zeros to maintain consistent precision:
+
+```
+> rat on
+Rational mode enabled
+> 10 3 -
+7.0000000000
+```
+
+Compare with float mode which uses `%.10g` formatting (suppressing trailing zeros):
+
+```
+> rat off
+Rational mode disabled (using float64)
+> 10 3 -
+7
+```
+
+## Precision Comparison
+
+### Division
+
+Division of integers produces the same displayed precision in both modes (10 significant digits), but rational mode stores the exact fraction internally:
+
+| Expression | Float mode | Rat mode | Notes |
+|---------------|------------------|------------------|--------------------|
+| `1 3 /` | `0.3333333333` | `0.3333333333` | Both truncated |
+| `1 7 /` | `0.1428571429` | `0.1428571429` | Both truncated |
+| `1 6 /` | `0.1666666667` | `0.1666666667` | Both truncated |
+| `10 3 /` | `3.3333333333` | `3.3333333333` | Both truncated |
+
+At 10 digits of precision, the displayed output is identical. The difference appears when chaining operations:
+
+### Chained division and multiplication
+
+| Expression | Float mode | Rat mode | Notes |
+|----------------|------------------|-------------------|---------------------------|
+| `3 10 / 3 *` | `0.9` | `0.9000000000` | Float loses precision |
+| `1 3 / 3 *` | `1` | `1.0000000000` | Both exact, different fmt |
+| `2 3 / 3 *` | `2` | `2.0000000000` | Both exact, different fmt |
+
+### Integer arithmetic
+
+Integer operations produce correct results in both modes:
+
+| Expression | Float mode | Rat mode |
+|-------------|------------|-----------------|
+| `1 2 +` | `3` | `3.0000000000` |
+| `10 3 -` | `7` | `7.0000000000` |
+| `3 4 *` | `12` | `12.0000000000` |
+
+### Decimal arithmetic limitations
+
+Rational mode stores exact rational representations when parsing decimal literals. However, the arithmetic operations (addition, subtraction, modulo) internally convert values to `float64` for metric-aware computation. This creates a known limitation:
+
+**Some decimal fractions fail in rational mode.** Decimals that cannot be exactly represented as float64 (such as `0.1`, `0.2`, `0.3`) cause errors when used with `+`, `-`, or `%` operators in rational mode:
+
+```
+> rat on
+Rational mode enabled
+> 0.1 0.2 +
+Error: rpn operator failed for '+': +: convertToBase: cannot convert rational number to float64
+```
+
+This happens because:
+
+1. `0.1` is parsed as the exact fraction `1/10`
+2. `big.Rat.Float64()` returns `ok=false` for `1/10` because `1/10` cannot be exactly represented as float64
+3. The `convertToBase` metric helper treats `ok=false` as an error
+
+**Decimals that are exact powers of 2 work correctly:** `0.5` (1/2), `0.25` (1/4), `0.75` (3/4), `0.125` (1/8), etc.
+
+```
+> rat on
+Rational mode enabled
+> 0.5 0.5 +
+1.0000000000
+> 0.25 0.75 +
+1.0000000000
+> 0.125 0.125 +
+0.2500000000
+```
+
+**Multiplication and division with any decimals work:** These operations bypass the problematic code path:
+
+```
+> rat on
+Rational mode enabled
+> 0.1 0.2 *
+0.0200000000
+> 0.3 0.1 /
+3.0000000000
+```
+
+> **Note:** This limitation exists in the current implementation. The `Rat.Float64()` method rejects conversions where the rational number cannot be exactly represented as float64, even though the conversion is valid for computation purposes. A future fix would allow lossy conversions to return the approximate float64 value.
+
+### Float mode precision issues
+
+Float mode exhibits classic IEEE 754 floating-point errors:
+
+```
+> rat off
+Rational mode disabled (using float64)
+> 0.3 0.1 - 0.2 -
+-2.775557562e-17
+```
+
+The result should be `0`, but floating-point rounding errors accumulate. In rational mode, the same expression fails with an error rather than producing a silently wrong result.
+
+## When to Use Rational Mode
+
+### Recommended use cases
+
+- **Integer arithmetic**: Exact results for integer addition, subtraction, multiplication, and division
+- **Dyadic decimals**: Values like `0.5`, `0.25`, `0.125` that are exact powers of 2
+- **Division results**: When you need to chain division results into further multiplication
+- **Avoiding silent float errors**: Rat mode fails loudly rather than returning an incorrect float result
+
+### Not recommended for
+
+- **General decimal arithmetic**: Values like `0.1`, `0.2`, `0.3` fail with `+`, `-`, `%` due to the float64 conversion limitation described above
+- **Single-command mode**: Rational mode is REPL-only; `gt 'rat on 1 2 +'` will not work
+- **Performance-sensitive calculations**: See performance trade-offs below
+
+## Performance Trade-offs
+
+Rational mode has higher computational overhead than float64 mode:
+
+| Factor | Float mode | Rat mode |
+|---------------------|------------|-------------------|
+| Number creation | CPU register | Heap allocation (`*big.Rat`) |
+| Memory per number | 8 bytes | ~48+ bytes (pointer + big.Ints) |
+| Arithmetic | Hardware FPU | Big integer arithmetic |
+| String formatting | `fmt.Sprintf` | `big.Rat.FloatString` |
+| Precision | ~15-17 digits | Arbitrary (limited by memory) |
+
+For most calculator use cases, the performance difference is negligible. However, for very large expressions or tight REPL loops, float mode will be noticeably faster.
+
+## Edge Cases
+
+### Division by zero
+
+Both modes return an error:
+
+```
+> rat on
+Rational mode enabled
+> 1 0 /
+Error: rpn operator failed for '/': /: division by zero
+```
+
+### Modulo by zero
+
+Both modes return an error:
+
+```
+> rat on
+Rational mode enabled
+> 10 0 %
+Error: rpn operator failed for '%': %: modulo by zero
+```
+
+### Insufficient operands
+
+Both modes return the same error:
+
+```
+> rat on
+Rational mode enabled
+> +
+Error: rpn operator failed for '+': +: insufficient operands for +: need at least 2 values
+```
+
+### Mode persistence in REPL
+
+Rational mode persists across REPL commands within the same session:
+
+```
+> rat on
+Rational mode enabled
+> 1 2 +
+3.0000000000
+> 3 4 *
+12.0000000000
+> 10 3 /
+3.3333333333
+> rat off
+Rational mode disabled (using float64)
+> 1 2 +
+3
+```
+
+### Fresh sessions start in float mode
+
+Each new gt session (single-command or new REPL) starts in float mode by default:
+
+```
+$ gt '1 2 +'
+3
+```
+
+### Mode and metrics
+
+Rational mode works with metric-aware operations, subject to the decimal conversion limitation described above. Integers and dyadic decimals with metrics work correctly:
+
+```
+> rat on
+Rational mode enabled
+> 100Mbps 50Mbps +
+Error: rpn operator failed for '+': +: convertToBase: cannot convert rational number to float64
+```
+
+The metric conversion path has the same float64 conversion issue. For metric calculations, float mode is recommended unless using integer or dyadic decimal values.
+
+### Mode and constants
+
+Constants (pi, e, etc.) are resolved as float64 values. In rational mode, they are wrapped in `Rat` types:
+
+```
+> rat on
+Rational mode enabled
+> pi
+3.1415926536
+```
+
+Note that `pi` is stored as a float64 constant and converted to `Rat` via `SetFloat64()`, so it carries the same float64 precision as float mode. It does not provide a more precise value of pi.
+
+### Mode and variables
+
+Variables store float64 values. When accessed in rational mode, they are converted to `Rat` via `SetFloat64()`:
+
+```
+> x 3.5 =
+x = 3.5000000000
+> rat on
+Rational mode enabled
+> x
+3.5000000000
+```
+
+## Summary
+
+| Feature | Float mode | Rat mode |
+|--------------------|------------|----------|
+| Default | Yes | No |
+| REPL-only | No | Yes |
+| Integer arithmetic | Exact | Exact |
+| Decimal arithmetic | Approx. | Exact (with limitations) |
+| `+`, `-` with 0.1 | Works (approx.) | Error |
+| `*`, `/` with 0.1 | Works | Works |
+| Metric operations | Full | Limited |
+| Output formatting | `%.10g` | `FloatString(10)` |
+| Memory usage | Low | Higher |
+| Performance | Fast | Slower |