property.floor-area Unreviewed
Floor area from room lengths and widths, exactly, with the total in both square metres and square feet.
1.0.2 · published 2026-10-03 by charlie · Anterra
Pinned by 20 tests, run in TypeScript, Python and Rust.
Unreviewed. This capability’s implementations agree in every language and pass its published test vectors, which were worked out from the official sources cited. But no qualified conveyancer or tax adviser has yet checked those vectors, or confirmed that the capability covers the cases it claims. Treat it as a draft. Do not use it for real people, money or decisions without your own expert review. Once a qualified reviewer signs off, this notice is replaced with their name, qualification and the date. Each new version needs fresh sign-off.
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 ftfloorArea(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.529999floorArea(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
| rooms | RoomDimensions[] | each room's length and width, as decimal text |
| lengthUnit | FloorAreaUnit | the unit the dimensions are measured in |
| decimals | int | 0 to 12; places for squareMetres and squareFeet, rounded half away from zero |
| returns | FloorArea |
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";
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
The manifest, vectors and README with only the TypeScript implementation. Install it without the registry with fune add ./property.floor-area-1.0.2-typescript.fune, or fetch it from a terminal with fune pull property.floor-area@1.0.2:typescript.
The whole function, every language, is one file too: property.floor-area-1.0.2.fune, 21,244 bytes, sha256 f59699a3b1318c87c90a61fe248c8ef838984c17b27452ee4d538dcd807727ed. 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.
| Case | Arguments | Expected | |
|---|---|---|---|
| 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 10 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| 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 |
| a trailing newline is not part of a length | rooms ×1, m, 2 | → | error: room "Hall": length must be a decimal below 1000 |
| a trailing newline is not part of a width | rooms ×1, m, 2 | → | error: room "Hall": width must be a decimal below 1000 |
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.
1.0.1 fixes Python accepting a trailing newline in a room's length or width; adds tests.
## Before you rely on this
**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 above, 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.
**Unreviewed.** This capability's implementations agree in every language and pass its published test vectors, which were worked out from the official sources cited. But no qualified conveyancer or tax adviser has yet checked those vectors, or confirmed that the capability covers the cases it claims. Treat it as a draft. Do not use it for real people, money or decisions without your own expert review. Once a qualified reviewer signs off, this notice is replaced with their name, qualification and the date. Each new version needs fresh sign-off.
1.0.2 marks it unreviewed. The code and the tests are unchanged.
Files
| Path | Bytes |
|---|---|
| README.md | 2,714 |
| impl/python.py | 2,108 |
| impl/rust.rs | 3,851 |
| impl/typescript.ts | 2,232 |
| vectors.json | 5,718 |