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);
}