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.
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.
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.