Reads a length typed in feet, inches, fractions or metric units into meters, and writes lengths, areas and volumes in the model's unit.
How the precision is kept
The model is in meters. A typed value is multiplied by its unit's factor in 64-bit; the inch (0.0254), foot (0.3048), yard (0.9144) and mile (1609.344) are the legal definitions, held as the nearest 64-bit numbers. Rounding happens only when a value is written as text: to a fraction of an inch or to a number of decimals. The stored length is not rounded.
Calculations under this policy
8 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.
Text such as 10'-6 1/2", 3/4", 320cm or a bare number, as meters.
m = sign × (feet × 0.3048 + (inches + num ÷ den) × 0.0254); m = sign × value × factor(suffix or model unit)
Feet and inches are each multiplied by their factor and then added: two products and one sum. The fraction is one 64-bit division. Metric suffixes use decimal factors (0.001, 0.01, 1e-6) that are the nearest 64-bit numbers.
m3d/units.js
lines 52 to 85
CadCraft 0.8.2
function parseOneLength(text, unit) {
if (typeof text !== 'string') return null;
let s = text.trim().replace(/[‘’′]/g, "'").replace(/[“”″]/g, '"').toLowerCase();
if (!s) return null;
let sign = 1;
if (s[0] === '-') { sign = -1; s = s.slice(1).trim(); }
else if (s[0] === '+') s = s.slice(1).trim();
const N = '(\\d+(?:\\.\\d*)?|\\.\\d+)';
// feet with optional inches and fraction: 10' 10'6" 10'-6 1/2" 10' 6.5
let m = s.match(new RegExp(`^${N}\\s*'\\s*(?:-?\\s*${N}?\\s*(?:(\\d+)\\s*/\\s*(\\d+))?\\s*"?)?$`));
if (m) {
const ft = +m[1], inch = m[2] ? +m[2] : 0, fr = m[3] && +m[4] ? +m[3] / +m[4] : 0;
return sign * (ft * FT + (inch + fr) * IN);
}
// inches with a fraction: 6 1/2" 6-1/2"
m = s.match(new RegExp(`^${N}\\s*[- ]\\s*(\\d+)\\s*/\\s*(\\d+)\\s*("|in)?$`));
if (m && +m[3]) {
const v = (+m[1] + +m[2] / +m[3]);
return sign * (unit === 'ft-in' || unit === 'in' || m[4] ? v * IN : v * unitScale(unit));
}
// a fraction alone: 3/4" 3/4
m = s.match(/^(\d+)\s*\/\s*(\d+)\s*("|in)?$/);
if (m && +m[2]) { const v = +m[1] / +m[2]; return sign * v * (m[3] ? IN : unitScale(unit)); }
// number + suffix
m = s.match(new RegExp(`^${N}\\s*(mm|cm|km|um|µm|μm|inches|inch|in|feet|foot|ft|yards|yard|yd|miles|mile|mi|mils|mil|thou|m|"|')?$`));
if (m) {
const v = +m[1];
if (!Number.isFinite(v)) return null;
return sign * v * (m[2] ? SUFFIX[m[2]] : unitScale(unit));
}
// every other length mark of the shared table (0.7 UNITS): 2 dm, 3 ch, 10 US ft, 400 nm, 1 nmi, 12 pt
const r = parseUnit(text, 'length'); // the text as typed: case matters there (nm is not NM, the nautical mile)
return r.ok ? r.value : null;
}
Meters per unit
64-bit
The factor that turns a bare number in the model's unit into meters.
in = 0.0254; ft = 0.3048; yd = 0.9144; mi = 1609.344; mil = 0.0254 ÷ 1000
A table lookup. The mil is computed as 0.0254 ÷ 1000 in 64-bit. A unit not in the table takes its factor from the shared unit table, or 1 when that has none.
m3d/units.js
lines 101 to 102
CadCraft 0.8.2
/** Meters per bare number in a unit (feet-and-inches: inches). */
export function unitScale(unit) { return { 'ft-in': IN, in: IN, ft: FT, yd: YD, mi: MI, mil: MIL, m: 1, cm: 0.01, mm: 0.001, um: 1e-6, km: 1000 }[unit] ?? (factor(unit) || 1); }
Arithmetic over typed lengths
64-bit
A typed expression such as 12'6"+3/4" or 2*450mm as one length in meters.
Each operand that carries a unit is converted to meters before the arithmetic; a bare number is passed on as a plain number and the unit factor is given separately. The arithmetic itself is done by lencalc.js.
m3d/units.js
lines 35 to 51
CadCraft 0.8.2
/**
* Arithmetic over typed lengths (0.7 MC-CALC; the rules of lencalc.js, never eval): a bare number is in the model's unit
* when it is used as a length (added to one, or the whole answer: 2.5/3 in a meter model is 0.833 m) and a plain factor
* when it multiplies or divides one (2*450mm). → {ok: true, value (meters), plain} | {ok: false, message (says where)} |
* null when the text holds no arithmetic.
*/
export function calcLength(text, unit = 'm') {
if (typeof text !== 'string') return null;
const s = text.trim().replace(/[‘’′]/g, "'").replace(/[“”″]/g, '"').toLowerCase();
if (!hasArithmetic(s)) return null;
const r = calc(s, (x) => {
const bare = /^[\d.\s/-]+$/.test(x.trim()), v = parseOneLength(x, bare ? 'm' : unit);
return v === null ? null : { value: v, unit: !bare };
}, { plain: unitScale(unit) });
if (!r.ok) return { ok: false, message: t('calc.err.' + r.code, { pos: r.pos + 1, text: r.text }) };
return { ok: true, value: r.value, plain: r.unit === 0 };
}
The value box
64-bit
What was typed as a length, a list of lengths, a point, an offset, a count or a plain number for angles and factors.
plain number = value in meters ÷ meters per unit (for arithmetic and bare fractions)
A bare decimal number is read directly as the plain number. For typed arithmetic or a bare fraction the plain number is the meter value divided by the unit factor, a multiply followed by a divide that can differ from the typed value in the last digit.
m3d/units.js
lines 175 to 214
CadCraft 0.8.2
/**
* The value box. Returns one of
* {kind: 'len', v} a length (m) 3m · 10'6"
* {kind: 'list', vs: [m…]} lengths separated by commas 4m,3m (rectangle width, height)
* {kind: 'abs', p} an absolute point [1,2,0]
* {kind: 'rel', p} an offset <1,0,0>
* {kind: 'sides', n} segments/sides 24s
* {kind: 'array', n} copies in a row 3x *3
* {kind: 'divide', n} copies between /3
* {kind: 'num', v} a plain number (angles, factors) — also offered as `num` on 'len'
* or null when it cannot be read.
*/
export function parseValue(text, unit = 'm') {
const s = (text || '').trim();
if (!s) return null;
let m;
if ((m = s.match(/^([[<])\s*(.+?)\s*([\]>])$/))) {
const parts = m[2].split(/[,;]/).map((x) => parseLength(x, unit));
if (parts.length !== 3 || parts.some((x) => x === null)) return null;
return { kind: m[1] === '[' ? 'abs' : 'rel', p: parts };
}
if ((m = s.match(/^(\d{1,4})\s*s$/i))) return { kind: 'sides', n: +m[1] };
if ((m = s.match(/^(?:(\d{1,4})\s*x|[*x]\s*(\d{1,4}))$/i))) return { kind: 'array', n: +(m[1] || m[2]) };
if ((m = s.match(/^\/\s*(\d{1,4})$/))) return { kind: 'divide', n: +m[1] };
if (/[,;]/.test(s)) {
const vs = s.split(/[,;]/).map((x) => (x.trim() ? parseLength(x, unit) : null));
if (vs.some((x) => x === null)) return null;
return { kind: 'list', vs };
}
// arithmetic (0.7 MC-CALC): a length, or for an angle or a factor the plain number it comes to (90/2 = 45)
const c = calcLength(s, unit);
if (c && !c.ok) return { kind: 'error', message: c.message };
if (c) return { kind: 'len', v: c.value, num: c.plain ? c.value / unitScale(unit) : null };
const v = parseLength(s, unit);
// a bare number (or a bare fraction: 90/2) is also offered as a plain number, for angles and factors
const num = /^[-+]?(\d+(\.\d*)?|\.\d+)$/.test(s) ? +s : v !== null && /^[-+]?[\d.\s/-]+$/.test(s) ? v / unitScale(unit) : null;
if (v !== null) return { kind: 'len', v, num };
if (num !== null) return { kind: 'num', v: num };
return null;
}
Length readout
64-bit
A length in meters as text in the model's unit.
ft-in: total = round(m ÷ 0.0254 × den) sixteenths (or the chosen fraction); others: toFixed with the unit's decimals
The division by the unit factor and the rounding happen only for the text. Feet and inches are split from one whole number of fractions of an inch, so a value that rounds up to 12 inches is written as the next foot. Default decimals: 4 for ft, 3 for yd and m, 2 for cm, 1 for mm.
m3d/units.js
lines 127 to 147
CadCraft 0.8.2
export function formatLength(m, unit = 'm') {
if (!Number.isFinite(m)) return '';
const neg = m < -1e-9, a = Math.abs(m);
let s;
if (unit === 'ft-in') {
const den = fracDen();
const total = Math.round((a / IN) * den);
const ft = Math.floor(total / (12 * den)), rest = (total - ft * 12 * den) / den;
s = ft ? `${ft}' ${inchText(rest)}"` : `${inchText(rest)}"`;
} else if (unit === 'in') s = inchText(a / IN) + '"';
else if (unit === 'ft') s = trim(a / FT, places(4)) + ' ft';
else if (unit === 'yd') s = trim(a / YD, places(3)) + ' yd';
else if (unit === 'mi') s = a >= MI ? trim(a / MI, places(4)) + ' mi' : trim(a / FT, places(3)) + ' ft'; // a mile model shows the small lengths in feet
else if (unit === 'mil') s = trim(a / MIL, places(2)) + ' mil';
else if (unit === 'um') s = trim(a * 1e6, places(2)) + ' µm';
else if (unit === 'mm') s = trim(a * 1000, places(1)) + ' mm';
else if (unit === 'cm') s = trim(a * 100, places(2)) + ' cm';
else if (unit === 'km') s = a >= 1000 ? trim(a / 1000, places(3)) + ' km' : trim(a, places(3)) + ' m'; // a kilometer model shows the small lengths in meters
else s = trim(a, places(3)) + ' m';
return (neg ? '-' : '') + s;
}
Inches as a fraction
64-bit
Inches written as a whole number and a reduced fraction.
sx = round(inches × den); whole = ⌊sx ÷ den⌋; fraction = (sx mod den) ÷ den, reduced by the greatest common divisor
One rounding to a whole number of fractions, then integer arithmetic. The denominator is 16 unless the file chose another power of two up to 64.
m3d/units.js
lines 117 to 125
CadCraft 0.8.2
/** Inches to "6 1/2" (nearest 1/16, or the chosen fraction). */
function inchText(inches) {
const den = fracDen();
const sx = Math.round(inches * den);
const whole = Math.floor(sx / den), frac = sx % den;
if (!frac) return String(whole);
const g = gcd(frac, den);
return (whole ? whole + ' ' : '') + frac / g + '/' + den / g;
}
Area readout
64-bit
An area in square meters as text in the model's unit.
ft²: a ÷ (0.3048 × 0.3048); in²: a ÷ (0.0254 × 0.0254); mm model: m² from 0.01 m², else mm²
The squared factor is formed by one multiplication and the area is divided by it. The text is rounded to 0 to 4 decimals depending on the unit.
m3d/units.js
lines 148 to 161
CadCraft 0.8.2
// millimeter models show areas from 0.01 m² and volumes from 0.01 m³ in square/cubic meters
// (a 4 × 3 m room reads "12 m²", not "12000000 mm²"); smaller ones stay in mm² / mm³
export function formatArea(a, unit = 'm') {
if (unit === 'ft-in' || unit === 'ft') return t('m3d.units.sqft', { v: trim(a / (FT * FT), 2) });
if (unit === 'yd') return t('m3d.units.sqyd', { v: trim(a / (YD * YD), 2) });
if (unit === 'mi') return Math.abs(a) >= MI * MI * 0.01 ? t('m3d.units.sqmi', { v: trim(a / (MI * MI), 3) }) : t('m3d.units.sqft', { v: trim(a / (FT * FT), 2) });
if (unit === 'um') return trim(a * 1e12, 1) + ' µm²';
if (unit === 'mil') return t('m3d.units.sqin', { v: trim(a / (IN * IN), 4) });
if (unit === 'in') return t('m3d.units.sqin', { v: trim(a / (IN * IN), 2) });
if (unit === 'mm') return Math.abs(a) >= 0.01 ? trim(a, 3) + ' m²' : trim(a * 1e6, 0) + ' mm²';
if (unit === 'cm') return trim(a * 1e4, 1) + ' cm²';
if (unit === 'km') return Math.abs(a) >= 1e6 ? trim(a / 1e6, 3) + ' km²' : trim(a, 3) + ' m²';
return trim(a, 3) + ' m²';
}
Volume readout
64-bit
A volume in cubic meters as text in the model's unit.
ft³: v ÷ 0.3048³; in³: v ÷ 0.0254³; mm model: m³ from 0.01 m³, else mm³
The cubed factor comes from the ** operator and the volume is divided by it. The text is rounded to 0 to 4 decimals depending on the unit.
Regular expressions pick the feet, inches, fraction and suffix out of the text; the parts are converted with unary plus and combined in one expression. Arithmetic in the typed text is handed to lencalc.js. Other unit marks go to the shared unit table.
Number types
64-bit numbers. The typed digits are converted by the language's own correctly rounded string-to-number conversion.
Tolerances
A length below −1e-9 m is shown with a minus sign. Readouts round to 1/16 inch (or the chosen 1/1 to 1/64) for feet and inches, and to a fixed number of decimals per unit otherwise.
Limits
Decimal factors such as 0.0254 and 0.001 are not exact in binary, so a typed value is within one rounding of the exact product. A plain number taken from typed arithmetic is converted to meters and divided back by the unit factor.
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.