Parsing Architectural Lengths
Wesley Zloza | Jun 10, 2025 | 3 min read
I recently found myself having to parse the length of an architectural input and to my surprise there wasn’t a package or snippet readily available (at least for TypeScript). The following code is the solution I’ve developed:
Parser
/**
* Architectural Length Regular Expression
*/
export const ArchitecturalPattern =
/^(?<sign>-?)((?<feet>\d+)'(?<separator>\s*-?\s*))?(?<inches>(?<integer>\d*)(\s*)(?<fraction>(\.\d+)|((?<numerator>\d+)\/(?<denominator>\d*)))?")?$/;
/**
* Tests whether the provided string (expression) represents a length in
* architectural format (e.g., X'-X X/X"). The expression can optionally have
* feet denoted with a single quote (') and inches as a compound fraction or
* decimal number, denoted with a double quote (").
* @param expression A length in architectural format, e.g., 4'-10 1/2".
*/
export function isArchitectural(expression: string): boolean {
return ArchitecturalPattern.test(expression.trim());
}
/**
* Parses the value of an architectural length. For example, an expression of
* 4'-10 1/2" would equate to 58.5 inches. An error is thrown if the provided
* expression is an invalid architectural length.
* @param expression A length in architectural format, e.g., 4'-10 1/2".
* @returns Length in inches.
*/
export function parseArchitectural(expression: string): number {
const isValidExpression = isArchitectural(expression);
if (!isValidExpression) throw new Error('Invalid Architectural Length');
const result = ArchitecturalPattern.exec(expression);
const groups = result?.groups;
const sign = groups?.sign ? -1 : 1;
const hasFraction = groups?.fraction !== undefined;
const isDecimal = groups?.numerator === undefined;
let inches = 0;
inches += groups?.feet ? Number.parseInt(groups.feet, 10) * 12 : 0;
inches += groups?.integer ? Number.parseInt(groups.integer, 10) : 0;
if (hasFraction && isDecimal) {
inches += Number.parseFloat(groups.fraction);
}
if (hasFraction && !isDecimal) {
const numerator = groups?.numerator
? Number.parseInt(groups.numerator, 10)
: 0;
const denominator = groups?.denominator
? Number.parseInt(groups.denominator, 10)
: 10;
inches += numerator / denominator;
}
return sign * inches;
}
Formatter
/**
* Converts a number in units of inches into architectural format (e.g., X'-X X/X").
* @description Converts inches into feet and displays the remainder of inches
* as a decimal-fraction number. For example, converts 53.5 into 4'-5 1/2",
* where single quotes (') signifies feet and double quotes (") signifies
* inches.
* @param length Length in inches.
* @param precision Fractional precision. Default: 1/16.
* @returns Length in architectural format.
*/
export function formatArchitectural(
length: number,
precision: number = 1 / 16,
): string {
const nominalLength = Math.abs(Math.round(length / precision) * precision);
const feet = Math.floor(nominalLength / 12);
const inches = Math.floor(nominalLength % 12);
const decimal = (nominalLength % 12) - inches;
let fraction: string | undefined;
if (decimal !== 0 && decimal !== 1) {
const fracDigits = decimal.toString().length - 2;
const denominator = 10 ** fracDigits;
const numerator = decimal * denominator;
const divisor = gcd(numerator, denominator);
fraction = `${Math.floor(numerator / divisor)}/${Math.floor(denominator / divisor)}`;
}
const sign = length < 0 ? '-' : '';
return `${sign}${feet}'-${inches}${fraction ? ` ${fraction}` : ''}"`;
}
/**
* Gets the greatest common divisor (GCD) between two number.
* @see https://stackoverflow.com/a/23575406
* @param num1 First number.
* @param num2 Second number.
* @returns Greatest common divisor.
*/
function gcd(num1: number, num2: number): number {
if (!num2) return num1;
return gcd(num2, num1 % num2);
}