Reads what is typed into a number field: plain numbers, lengths with units, angles, and expressions such as =3'6"/2.
How the precision is kept
Plain numbers and feet-and-inch lengths are read at full 64-bit precision. A length typed with a unit, and feet-inch or degrees-minutes-seconds values inside an expression, are first converted to text with 12 significant digits and then computed, so those values carry 12 digits, not 15 to 17. Results are checked against the field's limits with small fixed allowances.
Open item. Convert unit-suffixed lengths and degrees-minutes-seconds inside expressions as numbers rather than through 12-digit text.
Calculations under this policy
6 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.
Replaces 900mm, 2ft or 3'6" in the typed text by the same length in drawing units.
mm = feet × 304.8 + inches × 25.4, or value × mm per unit; text = (mm ÷ mm per drawing unit) to 12 significant digits
The converted length is put back into the text through editText, which keeps 12 significant digits, before the expression is computed. 7in in a drawing in feet becomes 0.583333333333 rather than 0.5833333333333334. In a drawing with a decimal length format, a length typed with a unit reaches this path even without an = sign, because readValue calls parseLength without the drawing's units.
propexpr.js
lines 54 to 69
CadCraft 0.8.2
/** Replace feet-inch and unit-suffixed literals by drawing units. → { src, used } (used = a unit was converted). */
function lengthsToUnits(src, mmU) {
let used = false;
const put = (mm) => { used = true; return mmU ? '(' + editText(mm / mmU) + ')' : '(0)'; };
let s = src.replace(FT_IN, (all, ft, wi, fn, fd, on, od, dec) => {
if (!/'/.test(all)) return all;
let inch = 0;
if (wi !== undefined) inch = +wi + (fn !== undefined ? fn / fd : 0);
else if (on !== undefined) inch = on / od;
else if (dec !== undefined) inch = +dec;
if ((fd !== undefined && +fd === 0) || (od !== undefined && +od === 0)) return all;
return put(ft * 304.8 + inch * 25.4);
});
s = s.replace(WITH_UNIT, (all, v, u) => put(v * SUFFIX_MM[u]));
return { src: s, used };
}
Degrees, minutes and seconds inside an expression
64-bit
Replaces 45°30'15" in an angle expression by decimal degrees.
degrees + minutes ÷ 60 + seconds ÷ 3600, written with 12 significant digits
The sum is exact to 64-bit, but it is put back into the text through editText: 45°30'15" becomes 45.5041666667. An angle typed alone, without an expression, does not take this path.
12 significant digits, fewer than the 17 that give back a 64-bit number exactly. The two conversions above use this text as a number.
propexpr.js
lines 41 to 47
CadCraft 0.8.2
/** Full precision for editing: 12 significant digits, no exponent for everyday sizes, no "-0". */
export function editText(v) {
if (!Number.isFinite(v)) return '';
const r = Number(v.toPrecision(12));
if (Object.is(r, -0) || r === 0) return '0';
return Math.abs(r) >= 1e-6 && Math.abs(r) < 1e15 ? String(+r.toFixed(Math.max(0, 11 - Math.floor(Math.log10(Math.abs(r)))))) : String(r);
}
Millimeters per drawing unit
Exact
The size of one drawing unit, or of the sheet's paper unit, in millimeters.
inch 25.4; foot 304.8; yard 914.4; mile 1 609 344
The table holds the defined sizes. On a sheet the paper unit is used: 25.4 for inches, otherwise 1.
propexpr.js
lines 33 to 39
CadCraft 0.8.2
/** Millimetres in one drawing unit (or the sheet's paper unit), or null when nobody said. */
export function mmPerUnit(ctx = {}) {
if (ctx.paper) return ctx.paper === 'in' ? 25.4 : 1;
const u = String(ctx.units || '').toLowerCase();
if (UNIT_MM[u]) return UNIT_MM[u];
return isImperialFormat(lunitCode(ctx.lunits ?? 2)) ? 25.4 : null;
}
Limits on a typed value
64-bit
Checks a result against the field's kind and limits.
whole number: |v − round(v)| ≤ 1e-9; v ≥ min − 1e-12; v ≤ max + 1e-12
All three allowances are absolute. A whole-number field rounds an accepted value to the nearest whole number; other kinds are stored as computed.
propexpr.js
lines 74 to 84
CadCraft 0.8.2
function finish(v, kind, ctx) {
if (!Number.isFinite(v)) return { ok: false, error: t('props.err.number') };
if (kind === 'int') {
if (Math.abs(v - Math.round(v)) > 1e-9) return { ok: false, error: t('props.err.int') };
v = Math.round(v);
}
if (ctx.gt0 && !(v > 0)) return { ok: false, error: t('props.err.gt0') };
if (ctx.min !== undefined && v < ctx.min - 1e-12) return { ok: false, error: t('props.err.min', { min: editText(ctx.min) }) };
if (ctx.max !== undefined && v > ctx.max + 1e-12) return { ok: false, error: t('props.err.max', { max: editText(ctx.max) }) };
return { ok: true, value: v };
}
Angles typed in radians, grads or degrees-minutes-seconds
64-bit
Reads 0.5r, 100g or 45°30'15" as degrees.
radians × (180 ÷ π); grads × 0.9; d + m ÷ 60 + s ÷ 3600
One multiplication by a 64-bit factor for radians and grads (0.9 is the nearest 64-bit number to 9⁄10). Degrees-minutes-seconds typed alone are summed at full precision here.
Unit-suffixed lengths and degrees-minutes-seconds are replaced in the text by a number in drawing units or degrees, then the expression is computed by expr.js.
Number types
64-bit numbers; intermediate text of 12 significant digits for converted lengths and angles.
Tolerances
A whole-number field accepts values within 1e-9 of a whole number (absolute). Minimum and maximum are checked with 1e-12 of slack (absolute).
Limits
A converted length such as 7in in a drawing in feet is kept as 0.583333333333, 12 significant digits.
Approach: 64-bit, stated limit
The arithmetic is 64-bit, but the method or the data sets the accuracy: a solver tolerance, a mesh, a step size, a datum shift, a table. The limit is stated where the result is shown.