Functional Weave
Code in Rust

math.exp

e to the power x from + - * / only, so TypeScript, Python and Rust return the same double for the same input.

1.0.0 (not the latest) · published 2026-10-03 by charlie · Anterra

Pinned by 10 tests, run in TypeScript, Python and Rust.

What it does

e^x computed with nothing but `+`, `-`, `*` and `/`, so TypeScript, Python and Rust return the same double for the same input. See `math.ln` for why the platform's `exp` is not good enough when three languages must agree to the bit.

## Method

For example

  • exp(0) → 1 e^0 is exactly 1
  • exp(1) → 2.718 e^1 is e
  • exp(-1) → 0.368 e^-1

The function

The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.

pub fn exp(x: f64) -> f64
xfloatbetween -708 and 709, where the result is a normal double
returnsfloatwithin a few units in the last place of the true value; bit-identical in every language

Your code names it in one line, in the file that uses it

fune!(math.exp@^1);  // then call exp(…)
impl/rust.rs · 48 lines · open · raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

use super::funejson::Value;  ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one

// fdlibm's split of ln 2: k * LN2_HI is exact for every k this range needs.
const LN2: f64 = 0.6931471805599453;
const LN2_HI: f64 = 6.93147180369123816490e-01;
const LN2_LO: f64 = 1.90821492927058770002e-10;

/// e^x using only +, -, * and /, in the same order as the TypeScript and
/// Python versions, so all three return the same double.
///
/// # Panics
/// Panics unless `x` is between -708 and 709.
pub fn exp(x: f64) -> f64 {
    if !x.is_finite() || x < -708.0 || x > 709.0 {
        panic!("exp needs x between -708 and 709, received {}", x);
    }
    let kf = (x / LN2 + 0.5).floor();
    let r = x - kf * LN2_HI - kf * LN2_LO;
    let mut p = 1.0 / 20922789888000.0;
    p = 1.0 / 1307674368000.0 + r * p;
    p = 1.0 / 87178291200.0 + r * p;
    p = 1.0 / 6227020800.0 + r * p;
    p = 1.0 / 479001600.0 + r * p;
    p = 1.0 / 39916800.0 + r * p;
    p = 1.0 / 3628800.0 + r * p;
    p = 1.0 / 362880.0 + r * p;
    p = 1.0 / 40320.0 + r * p;
    p = 1.0 / 5040.0 + r * p;
    p = 1.0 / 720.0 + r * p;
    p = 1.0 / 120.0 + r * p;
    p = 1.0 / 24.0 + r * p;
    p = 1.0 / 6.0 + r * p;
    p = 1.0 / 2.0 + r * p;
    p = 1.0 + r * p;
    let mut result = 1.0 + r * p;
    let k = kf as i64;
    for _ in 0..k.max(0) {
        result *= 2.0;
    }
    for _ in 0..(-k).max(0) {
        result /= 2.0;
    }
    result
}

pub fn fune_vector(args: &[Value]) -> Value {
    Value::Float(exp(args[0].as_f64()))
}

Install

fune build

With that line in your source, in a Rust project (language rust in fune.project), fune build resolves it and nothing else, pins them in fune.lock, downloads only the Rust package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. A crate’s build.rs runs it before every compile. Or pin a range in fune.project and build in one step:

fune add math.exp
Download for Rust math.exp-1.0.0-rust.fune · 4,801 bytes sha256 1cb42c50f734a43c106a470079eea46df540371ca5cc34f6229a3f7ff22725bc

The manifest, vectors and README with only the Rust implementation. Install it without the registry with fune add ./math.exp-1.0.0-rust.fune, or fetch it from a terminal with fune pull math.exp@1.0.0:rust.

The whole function, every language, is one file too: math.exp-1.0.0.fune, 7,558 bytes, sha256 29423e4cf6d386b02f5d4bbd7a4e669544e1b6ac82033d114c726f51d4ef223a. It installs into a project of any language.

Customise it in your app

The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.

before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.

// fune: before math.exp

after — your function gets the result and the arguments, and returns the final result.

// fune: after math.exp

replace — it requires no other capability, so there is no dependency to replace.

step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show math.exp --steps.

// fune: step math.exp after <n|label>

Tests

A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.

CaseArgumentsExpected
e^0 is exactly 1 0 → 1
e^1 is e 1 → 2.718
e^-1 -1 → 0.368
e^(ln 2) is 2 0.693 → 2
e^10 10 → 22,026.466
e^0.5 is the square root of e 0.5 → 1.649
near the top of the range 700 → 10,142,320,547,350,045,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,…
near the bottom of the range -700 → 0
above 709 overflows and is an error 710 → error: exp needs x between -708 and 709
below -708 is an error -709 → error: exp needs x between -708 and 709

More from the author

1. x = k ln 2 + r with k = floor(x / ln 2 + 1/2) and |r| <= ln 2 / 2, reduced with ln 2 in two parts (Cody and Waite) so the remainder keeps its digits. 2. e^r by its Taylor series to r^16 / 16!, by Horner's rule; the coefficients are correctly rounded divisions of exact integers. 3. Multiply by 2^k by exact doubling or halving.

The result is within 1 unit in the last place of the true value.

x must lie between -708 and 709, where e^x is a normal double; outside that the answer would be infinity or lose precision as a subnormal, and it is an error instead. (`math.pow` treats results below the normal range as 0.)

Sources: W. J. Cody and W. Waite, *Software Manual for the Elementary Functions* (1980); Sun Microsystems fdlibm, `e_exp.c` (the ln 2 split).

Files

PathBytes
README.md1,028
impl/python.py1,196
impl/rust.rs1,452
impl/typescript.ts1,438
vectors.json844