Functional Weave
Code in TypeScript

property.floor-area

Floor area from room lengths and widths, exactly, with the total in both square metres and square feet.

1.0.0 (not the latest) · published 2026-10-03 by charlie · Anterra

Pinned by 18 tests, run in TypeScript, Python and Rust.

Not professional advice. This capability calculates property figures from published rules. It is a software component for developers, not legal or financial advice. Rules change and every rate here has an effective date. Check that the dates cover your case. Verify results against the official sources listed in its README, and have a conveyancer or tax adviser review how you use it, before anyone relies on the output. Provided “as is” under its licence, without warranty.

What it does

The floor area of a property from its room measurements, as estate agents' floor plans and EPCs quote it: every room's length × width, their total, and the total in both square metres and square feet.

`floorArea([{name: "Lounge", length: "4", width: "3"}, {name: "Bed", length: "2.5", width: "2"}], "m", 2)` gives rooms of 12 and 5 m², a total of 17 m², and 182.99 sq ft.

For example

  • floorArea(rooms ×2, m, 2) → room areas 12, 5, total 17, square metres 17, square feet 182.99 a 4 × 3 m lounge and a 2.5 × 2 m bedroom: 17 m², 182.99 sq ft
  • floorArea(rooms ×1, m, 2) → room areas 13.53, total 13.53, square metres 13.53, square feet 145.64 decimal dimensions multiply exactly: 4.1 × 3.3 is 13.53, not 13.529999
  • floorArea(rooms ×2, ft, 2) → room areas 120, 174.375, total 294.375, square metres 27.35, square feet 294.38 measured in feet: 12 × 10 and 15.5 × 11.25 is 294.375 sq ft, 27.35 m²

The function

The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.

export function floorArea(rooms: readonly RoomDimensions[], lengthUnit: FloorAreaUnit, decimals: number): FloorArea
roomsRoomDimensions[]each room's length and width, as decimal text
lengthUnitFloorAreaUnitthe unit the dimensions are measured in
decimalsint0 to 12; places for squareMetres and squareFeet, rounded half away from zero
returnsFloorArea

The types it declares, generated into your project

export type FloorAreaUnit = "m" | "ft";

/** One rectangular room or area. */
export interface RoomDimensions {
  readonly name: string;
  /** plain decimal text, at most 3 decimal places, greater than 0 and below 1000 */
  readonly length: string;
  /** the same */
  readonly width: string;
}

/** Room areas and the total, exact in the measured unit and rounded in both. */
export interface FloorArea {
  /** each room's length × width, exact, in square lengthUnit, in input order */
  readonly roomAreas: readonly string[];
  /** the sum of roomAreas, exact, in square lengthUnit */
  readonly total: string;
  /** the total in m², rounded to decimals */
  readonly squareMetres: string;
  /** the total in sq ft, rounded to decimals */
  readonly squareFeet: string;
}

Your code names it in one line, in the file that uses it

import { floorArea } from "#fune/property.floor-area@^1";
impl/typescript.ts · 51 lines · open · 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),
  };
}

Install

fune build

With that line in your source, in a TypeScript project (language typescript in fune.project), fune build resolves it and its 1 dependency, pins them in fune.lock, downloads only the TypeScript package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. Or pin a range in fune.project and build in one step:

fune add property.floor-area
Download for TypeScript property.floor-area-1.0.0-typescript.fune · 13,139 bytes sha256 b378c9cae199090a499f7a0e51a7e3381360d179d6b7785e254050fde65b1661

The manifest, vectors and README with only the TypeScript implementation. Install it without the registry with fune add ./property.floor-area-1.0.0-typescript.fune, or fetch it from a terminal with fune pull property.floor-area@1.0.0:typescript.

The whole function, every language, is one file too: property.floor-area-1.0.0.fune, 19,410 bytes, sha256 1d61ab42c79adb922f1fa4e036ba46b288c591b0579a04ac64e2de69a39b9d9b. It installs into a project of any language.

Customise it in your app

The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.

before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.

// fune: before property.floor-area

after — your function gets the result and the arguments, and returns the final result.

// fune: after property.floor-area

replace — inside this capability’s code only, calls to a dependency go to your function, with the same signature. Other capabilities that use it are unaffected; write in * to replace it everywhere.

// fune: replace units.convert in property.floor-area

step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show property.floor-area --steps.

// fune: step property.floor-area after <n|label>

Tests

A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.

CaseArgumentsExpected
a 4 × 3 m lounge and a 2.5 × 2 m bedroom: 17 m², 182.99 sq ft rooms ×2, m, 2 → room areas 12, 5, total 17, square metres 17, square feet 182.99
decimal dimensions multiply exactly: 4.1 × 3.3 is 13.53, not 13.529999 rooms ×1, m, 2 → room areas 13.53, total 13.53, square metres 13.53, square feet 145.64
measured in feet: 12 × 10 and 15.5 × 11.25 is 294.375 sq ft, 27.35 m² rooms ×2, ft, 2 → room areas 120, 174.375, total 294.375, square metres 27.35, square feet 294.38
the same at 6 places keeps the exact square feet rooms ×2, ft, 6 → room areas 120, 174.375, total 294.375, square metres 27.348332, square feet 294.375
100 sq ft to whole square metres is 9 rooms ×1, ft, 0 → room areas 100, total 100, square metres 9, square feet 100
100 sq ft to 4 places is 9.2903 m² rooms ×1, ft, 4 → room areas 100, total 100, square metres 9.2903, square feet 100
one square metre at 12 places: 10.76391041671 sq ft rooms ×1, m, 12 → room areas 1, total 1, square metres 1, square feet 10.76391041671
millimetre precision: 0.001 × 0.001 m is a square millimetre rooms ×1, m, 6 → room areas 0.000001, total 0.000001, square metres 0.000001, square feet 0.000011
the largest room: 999.999 × 999.999 m rooms ×1, m, 0 → room areas 999998.000001, total 999998.000001, square metres 999998, square feet 10763889
trailing zeros are accepted and dropped: 3.50 × 2.00 rooms ×1, m, 2 → room areas 7, total 7, square metres 7, square feet 75.35
Show the other 8 tests
CaseArgumentsExpected
no rooms is refused , m, 2 → error: rooms must not be empty
a zero dimension is refused rooms ×1, m, 2 → error: room "Cupboard": length must be greater than zero
four decimal places is refused rooms ×1, m, 2 → error: room "Hall": length must be a decimal below 1000
1000 or more is refused rooms ×1, m, 2 → error: room "Field": length must be a decimal below 1000
feet and inches are not decimal feet rooms ×1, ft, 2 → error: room "Hall": length must be a decimal below 1000
a negative width is refused rooms ×1, m, 2 → error: room "Hall": width must be a decimal below 1000
an unknown unit is refused rooms ×1, yd, 2 → error: unknown length unit
decimals beyond 12 are refused by units.convert rooms ×1, m, 13 → error: decimals must be a whole number from 0 to 12

More from the author

## Why decimal text

Dimensions are read as exact decimals, never floats, so 4.1 × 3.3 is 13.53, not 13.529999999999999. Each room's area and the total are exact (up to six decimal places, from dimensions with up to three), and the only rounding is the final one to `decimals` places in each unit, done by `units.convert` with the exact definition 1 ft = 0.3048 m (1 sq ft = 0.09290304 m²). The total is rounded once, not the rooms: rounding each room first and adding can be off in the last place.

## Edge cases and limits

- Rooms are rectangles. An L-shaped room is two entries; a bay is its own entry; deduct nothing, since the function never subtracts. - Dimensions are greater than 0 and below 1000 of the unit, with at most 3 decimal places (a millimetre in metres), and there may be at most 1000 rooms. An empty list is an error, as is a room of zero size. - Feet and inches must be converted to decimal feet first (10 ft 6 in is "10.5"). - This is the area of the rooms measured, not a RICS gross internal area: walls, stairs and circulation space count only if they are measured in.

Files

PathBytes
README.md1,506
impl/python.py2,106
impl/rust.rs3,851
impl/typescript.ts2,232
vectors.json5,223