Holds every unit's size as an exact fraction, reads typed values such as 12'6 1/2", 45°30'15" or 3:1, and writes values as feet and inches, degrees-minutes-seconds or decimals.
How the precision is kept
Each unit is a pair of whole numbers n and d below 2^53 taken from its legal definition, so n ÷ d has one rounding. Typed feet and inches, fractions and degrees-minutes-seconds are combined with one multiplication or division per part. When writing, the value is rounded once to a whole number of the smallest step and then split with whole-number arithmetic.
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.
Builds each unit's row: its fraction of the base unit and the factor used for quick conversion.
factor = n ÷ d (× π for degrees and gons)
n and d are whole numbers written in the table above, for example 254 ÷ 10000 for the inch and 1200 ÷ 3937 for the US survey foot. factor is the one rounded 64-bit value made from them; for degrees and gons it also carries the rounding of Math.PI.
One division and one addition. A zero denominator is refused, and so is a mixed number whose whole part has a decimal point or an exponent.
unit-table.js
lines 173 to 180
CadCraft 0.8.2
/** "6", "6.5", "6 1/2", "6-1/2", "1/2" → a number, else null. */
export function readNumber(s) {
const m = MIXED.exec(String(s).trim());
if (!m) return null;
if (m[4] !== undefined) return +m[5] ? m[4] / m[5] : null;
if (m[2] !== undefined) return +m[3] && !/[.eE]/.test(m[1]) ? +m[1] + m[2] / m[3] : null;
return +m[1];
}
Feet and inches
64-bit
Reads 12'6", 12'-6 1/2" or 6" as inches.
inches = feet × 12 + inches
Feet are multiplied by the whole number 12 and added to the inches, each read by readNumber. A bare number without a foot or inch mark is not read here.
unit-table.js
lines 229 to 238
CadCraft 0.8.2
/** 12' 12'6" 12' 6 1/2" 12'-6-1/2" 12'-6 6" 6 1/2" 1/2" → inches, else null. */
function feetInches(s) {
if (!/\d/.test(s)) return null;
const m = /^(?:([\d./ ]+?)\s*')?\s*(?:-?\s*([\d./ -]+?)\s*"?)?$/.exec(s.replace(/\s*-\s*(?=\d+\s*\/)/g, ' '));
if (!m || (m[1] === undefined && m[2] === undefined)) return null;
if (m[1] === undefined && !/"$/.test(s)) return null; // 6 alone is not feet-and-inches
const ft = m[1] !== undefined ? readNumber(m[1]) : 0, inch = m[2] !== undefined && m[2].trim() !== '' ? readNumber(m[2].replace(/-/g, ' ')) : 0;
if (ft === null || inch === null) return null;
return ft * 12 + inch;
}
Degrees, minutes and seconds
64-bit
Reads 45°30'15" or 45d30m15s as degrees.
degrees + minutes ÷ 60 + seconds ÷ 3600
Two divisions by whole numbers and two additions in 64-bit. Minutes or seconds of 60 or more are refused.
unit-table.js
lines 239 to 246
CadCraft 0.8.2
/** 45°30'15" 45° 30' 45d30m15s 45° → degrees, else null. */
function dms(s) {
const m = /^(\d+(?:\.\d+)?)\s*(?:°|d)\s*(?:(\d+(?:\.\d+)?)\s*(?:'|m))?\s*(?:(\d+(?:\.\d+)?)\s*(?:"|s))?$/i.exec(s);
if (!m) return null;
const mi = m[2] === undefined ? 0 : +m[2], se = m[3] === undefined ? 0 : +m[3];
if (mi >= 60 || se >= 60) return null;
return +m[1] + mi / 60 + se / 3600;
}
Slopes
64-bit
Reads 2%, 3H:1V, 1:3 or 6/12 as a grade (rise over run).
percent ÷ 100; H:V → V ÷ H; rise:run → rise ÷ run
One division each. A zero run in the rise:run form is refused outright; a zero H in an H:V form divides by zero and is then refused because the result is not finite.
unit-table.js
lines 247 to 261
CadCraft 0.8.2
/** 2% 3H:1V 1V:3H 3:1 (H:V field) / 1:3 (rise:run) 6/12 6 in 12 → { ok, value (grade), unit }, else null. */
function slope(s, bareUnit) {
let m = new RegExp('^(' + NUM + ')\\s*%$').exec(s);
if (m) return done(+m[1] / 100, 'pct');
m = new RegExp('^(' + NUM + ')\\s*H\\s*:\\s*(' + NUM + ')\\s*V$', 'i').exec(s);
if (m) return done(+m[2] / +m[1], 'hv');
m = new RegExp('^(' + NUM + ')\\s*V\\s*:\\s*(' + NUM + ')\\s*H$', 'i').exec(s);
if (m) return done(+m[1] / +m[2], 'riserun');
m = new RegExp('^(' + NUM + ')\\s*(:|/|in)\\s*(' + NUM + ')$', 'i').exec(s);
if (m) {
if (m[2] === ':' && bareUnit === 'hv') return done(+m[3] / +m[1], 'hv');
return +m[3] ? done(+m[1] / +m[3], 'riserun') : fail('number');
}
return null;
}
Writing feet and inches
64-bit
Writes a length as feet, inches and a fraction of an inch.
The length is rounded once to a whole number of 1/den inch (den is 16 unless stated). Feet, inches and the fraction are then split with whole-number arithmetic, so a value that rounds up to 12 inches carries into the feet.
unit-table.js
lines 280 to 287
CadCraft 0.8.2
if (id === 'ft-in') {
const neg = value < 0;
let n = Math.round(Math.abs(fromBase(value, 'in')) * den);
const ft = Math.floor(n / (12 * den)); n -= ft * 12 * den;
const inch = Math.floor(n / den), [fn, fd] = reduce(n - inch * den, den);
const zero = !ft && !inch && !fn;
return (neg && !zero ? '-' : '') + ft + "'-" + inch + (fn ? ' ' + fn + '/' + fd : '') + '"';
}
Writing degrees, minutes and seconds
64-bit
Writes an angle as degrees, minutes and seconds.
t = round(|degrees| × 3600 × 10^dec); split by 3600 and 60
The angle is rounded once to a whole number of the smallest shown part of a second, then split with whole-number arithmetic, so seconds cannot print as 60.
unit-table.js
lines 288 to 296
CadCraft 0.8.2
if (id === 'dms') {
const neg = value < 0;
const p = Math.pow(10, d);
let t = Math.round(Math.abs(fromBase(value, 'deg')) * 3600 * p);
const dg = Math.floor(t / (3600 * p)); t -= dg * 3600 * p;
const mi = Math.floor(t / (60 * p)); t -= mi * 60 * p;
const sec = (t / p).toFixed(d);
return (neg && (dg || mi || +sec) ? '-' : '') + dg + '°' + String(mi).padStart(2, '0') + "'" + sec.padStart(d ? 3 + d : 2, '0') + '"';
}
Writing decimals
64-bit
Writes a number with a fixed count of decimals.
text = v.toFixed(dec)
The number is rounded by toFixed to the unit's decimals for the text only; a result of −0 prints as 0 and trailing zeros can be dropped.
unit-table.js
lines 263 to 269
CadCraft 0.8.2
// ---- format ----
function num(v, dec, trim) {
const r = Number(v.toFixed(dec));
let s = (Object.is(r, -0) ? 0 : r).toFixed(dec);
if (trim && s.includes('.')) s = s.replace(/\.?0+$/, '');
return s;
}
In detail
Method
Fractions n ÷ d of the base unit (× π for degrees and gons); the file's header names the source of each definition.
Number types
64-bit numbers; whole numbers for n, d and for the split into feet, inches and fraction or degrees, minutes and seconds.
Limits
Decimal output is rounded to the stated number of decimals. A mixed number is refused when its whole part has a decimal point.
Approach: Exact values
Constants and factors are held as exact fractions from their definitions. The arithmetic is ordered so that no rounded factor is ever used.