Resources Numerical accuracy Calculation policy

Arithmetic on typed lengths

Works out a length typed as an expression, such as 4+7/16 or 2*450mm, wherever a length is asked for.

How the precision is kept

Each length is read by the app's own parser and combined with + − × ÷ in 64-bit numbers in the order the expression gives. The result is not rounded. Division by exactly zero and any result that is not a finite number are refused with the position in the text.

Calculations under this policy

4 calculations in 1 source file. The code is read from the released app when this page loads. It is the calculation people run, not a copy kept for this page.

lencalc.js CadCraft 0.8.2 64-bit 4 calculations Reviewed

Tests tools/lencalc/test.mjs, bin/test-civilcraft-lengths.mjs

abs, round, ceil and floor

64-bit

The four functions an expression may call.

round(x) = floor(x + 0.5) for x ≥ 0, −floor(−x + 0.5) for x < 0; ceil(x) = ⌈x − 1e-9·max(1, |x|)⌉; floor(x) = ⌊x + 1e-9·max(1, |x|)⌋

Halves round away from zero. x + 0.5 is itself a rounded sum, so the largest 64-bit number below 0.5 (0.49999999999999994) rounds to 1. ceil and floor shift by 1e-9 × max(1, |x|) first, so a value within that distance past a whole number goes to that whole number.

  • lencalc.js
  • lines 41 to 46
  • CadCraft 0.8.2
const FUNCS = Object.assign(Object.create(null), {
  abs: Math.abs,
  round: (x) => (x < 0 ? -Math.floor(-x + 0.5) : Math.floor(x + 0.5)),
  ceil: (x) => Math.ceil(x - 1e-9 * Math.max(1, Math.abs(x))),
  floor: (x) => Math.floor(x + 1e-9 * Math.max(1, Math.abs(x))),
});

Adding and subtracting

64-bit

Sums and differences of lengths, left to right.

One 64-bit addition or subtraction per operator, in the order typed. A plain number added to a length is first multiplied by the caller's factor for a bare number. A result that is not finite is refused.

  • lencalc.js
  • lines 117 to 125
  • CadCraft 0.8.2
function expr() {
  let a = term();
  while (peek().t === '+' || peek().t === '-') {
    const o = toks[k++], b = term();
    const av = a.u < b.u ? a.v * plain : a.v, bv = b.u < a.u ? b.v * plain : b.v;   // a plain number added to a length is one
    a = { v: fin(o.t === '+' ? av + bv : av - bv, o.pos), u: Math.max(a.u, b.u) };
  }
  return a;
}

Multiplying and dividing

64-bit

Products and quotients of a length and plain numbers, left to right.

One 64-bit multiplication or division per operator. Dividing by exactly 0 is refused. Two lengths multiplied, or a plain number divided by a length, are refused by keeping count of the unit, not by looking at the value.

  • lencalc.js
  • lines 126 to 137
  • CadCraft 0.8.2
function term() {
  let a = unary();
  while (peek().t === '*' || peek().t === '/') {
    const o = toks[k++], b = unary();
    if (o.t === '/' && b.v === 0) throw new CalcError('divide', o.pos);
    const u = o.t === '*' ? a.u + b.u : a.u - b.u;
    if (u > 1) throw new CalcError('area', o.pos);
    if (u < 0) throw new CalcError('ratio', o.pos);
    a = { v: fin(o.t === '*' ? a.v * b.v : a.v / b.v, o.pos), u };
  }
  return a;
}

The answer

64-bit

The final value and whether it is a length or a plain number.

The value is returned as computed, with no rounding. A wholly plain number is multiplied by the caller's bare-number factor once at the end.

  • lencalc.js
  • lines 171 to 174
  • CadCraft 0.8.2
const r = expr();
if (peek().t !== 'end') throw new CalcError('extra', peek().pos, s[peek().pos] || '');
const single = toks.length === 2 || (toks.length === 3 && toks[0].t === 'sign');
return { ok: true, value: fin(r.u ? r.v : r.v * plain, 0), unit: r.u, plain: single };

In detail

Method
A recursive-descent parser (expr, term, unary, primary) following the grammar in the file's header; no eval. Functions abs, round, ceil and floor from a fixed table.
Number types
64-bit numbers.
Tolerances
ceil and floor allow 1e-9 × max(1, |x|), so a value that close to a whole number goes to that whole number.
Limits
A fraction such as 7/16 is one division; a fraction whose denominator is not a power of two is not exact in binary. Text over 500 characters or 50 levels deep is refused.

Approach: 64-bit, direct

Every value is a 64-bit number from input to result. Formulas are closed form or a direct solve, and every comparison uses a stated tolerance.

Every policy that takes this approach