Functional Weave
Code in TypeScript

property.floor-area@1.0.2

impl/typescript.ts

2,232 bytes · the TypeScript implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

import { convertUnits } from "./units_convert.ts";  ← from units.convert ^1.0.0 · built alongside by fune
import { type FloorArea, type FloorAreaUnit, type RoomDimensions } from "./property_floor_area_types.ts";

const DIMENSION = /^[0-9]{1,3}(\.[0-9]{1,3})?$/;
const MAX_ROOMS = 1000;

/** A dimension as an integer number of thousandths. */
function thousandths(room: string, field: string, text: string): number {
  if (typeof text !== "string" || !DIMENSION.test(text)) {
    throw new RangeError(`room "${room}": ${field} must be a decimal below 1000 with at most 3 decimal places, received "${text}"`);
  }
  const [whole, fraction = ""] = text.split(".");
  const value = Number(whole) * 1000 + Number(fraction.padEnd(3, "0"));
  if (value === 0) {
    throw new RangeError(`room "${room}": ${field} must be greater than zero`);
  }
  return value;
}

/** An integer number of millionths as the shortest exact decimal. */
function decimal(millionths: number): string {
  const whole = Math.floor(millionths / 1_000_000);
  const fraction = String(millionths % 1_000_000).padStart(6, "0").replace(/0+$/, "");
  return fraction === "" ? String(whole) : `${whole}.${fraction}`;
}

/**
 * Room areas and their total, exact, from length × width of each room, and
 * the total in square metres and square feet rounded once at the end.
 */
export function floorArea(rooms: readonly RoomDimensions[], lengthUnit: FloorAreaUnit, decimals: number): FloorArea {
  if (lengthUnit !== "m" && lengthUnit !== "ft") {
    throw new RangeError(`unknown length unit "${lengthUnit}": use m or ft`);
  }
  if (rooms.length === 0) {
    throw new RangeError("rooms must not be empty");
  }
  if (rooms.length > MAX_ROOMS) {
    throw new RangeError(`at most ${MAX_ROOMS} rooms, received ${rooms.length}`);
  }
  // Each area is below 1e12 millionths and the total below 1e15: exact numbers.
  const areas = rooms.map((r) => thousandths(r.name, "length", r.length) * thousandths(r.name, "width", r.width));
  const total = decimal(areas.reduce((a, b) => a + b, 0));
  const from = lengthUnit === "m" ? "m2" : "ft2";
  return {
    roomAreas: areas.map(decimal),
    total,
    squareMetres: convertUnits(total, from, "m2", decimals),
    squareFeet: convertUnits(total, from, "ft2", decimals),
  };
}