Resources Numerical accuracy Calculation policy

Joist depth from a span table the person loads

Picks the smallest joist depth whose listed clear span reaches the span asked for, from a table the person loads.

How the precision is kept

The code holds no span numbers of its own. The table the person loads sets the accuracy: lengths are converted to millimeters in 64-bit with the exact factors 25.4 and 304.8, and the pick is a comparison against the listed spans with a stated tolerance.

Calculations under this policy

6 calculations in 1 source file. The code is read from the released app when this page loads. It is the calculation people run, not a copy kept for this page.

tji-table.js BimCraft 0.8.1 64-bit 6 calculations Reviewed

Tests tools/bimcraft-test/gh3-test.mjs

Span text to millimeters

64-bit

A listed span, as a number or as feet-inches text, in millimeters.

mm = ft × 304.8 + (in + n ÷ d) × 25.4

Feet times 304.8 plus inches times 25.4, both exact factors, with a fraction added to the inches before converting. Metric spans multiply meters by 1000. A zero or negative span gives NaN, which the loader refuses.

  • tji-table.js
  • lines 34 to 43
  • BimCraft 0.8.1
/** "15-6" = 15 ft 6 in; "15-6 1/2" too; a plain number is feet (imperial) or metres (metric). → mm, or NaN. */
function spanMm(v, metric) {
  if (metric) { const n = num(v); return n > 0 ? n * 1000 : NaN; }
  if (typeof v === 'number') return v > 0 ? v * FT : NaN;
  const s = String(v ?? '').trim();
  const m = /^(\d+)\s*-\s*(\d+(?:\.\d+)?)(?:\s+(\d+)\/(\d+))?$/.exec(s);
  if (m) return +m[1] * FT + (+m[2] + (m[3] ? +m[3] / +m[4] : 0)) * IN;
  const n = Number(s);
  return n > 0 ? n * FT : NaN;
}

Table units to millimeters

64-bit

Depth, spacing and bearing of the loaded table in millimeters.

Imperial depths, spacings and bearings are multiplied by 25.4 (exact) as read; metric ones by 1. Each number is converted once; nothing is rounded.

  • tji-table.js
  • lines 94 to 103
  • BimCraft 0.8.1
const series = String(r?.series ?? '').trim(), depth = num(r?.depth) * u;
if (!series || !(depth > 0)) { errors.push(bad('tji.err.row', { series: series || '?' })); continue; }
const spans = new Map();
for (const [cid, bySp] of Object.entries(r.spans || {})) {
  if (!cases.some((c) => c.id === cid)) { errors.push(bad('tji.err.case', { id: cid })); continue; }
  const list = [];
  for (const [sp, v] of Object.entries(bySp || {})) {
    const spacing = num(sp) * u, max = spanMm(v, metric);
    if (!(spacing > 0) || !(max > 0)) { errors.push(bad('tji.err.span', { series, case: cid, spacing: sp })); continue; }
    list.push({ spacing, max });

Spacing match

64-bit

Whether a requested on-center spacing is served by a listed column.

0.98 × listed ≤ want ≤ 1.005 × listed

Relative band: 2% closer or 0.5% wider than the listed spacing, so 400 mm serves a listed 16 in (406.4 mm) and 600 mm serves 24 in (609.6 mm).

  • tji-table.js
  • lines 118 to 119
  • BimCraft 0.8.1
/** A listed spacing column serves a requested spacing equal to it or up to 2% CLOSER (400 mm for 16 in, 600 mm for 24 in): never a wider one. */
const spacingFits = (listed, want) => want <= listed * 1.005 && want >= listed * 0.98;

Smallest depth that reaches the span

64-bit

The joist size for a span, spacing and load case.

first candidate with max + 10⁻⁶ ≥ span

Sorts candidates by depth then listed span and takes the first with max + 1e-6 >= span; the 1e-6 mm is absolute. It refuses a spacing or a span the table does not list.

  • tji-table.js
  • lines 120 to 138
  • BimCraft 0.8.1
/**
 * The smallest depth (then the shortest-reaching series at that depth) whose listed maximum clear span, for this case and this on-centre
 * spacing, reaches `span` (mm). Refuses, with a plain message, a case or spacing the table does not list and a span beyond every row.
 */
export function pickTji(table, { span, spacing, caseId } = {}) {
  if (!table) return bad('tji.err.none');
  const cs = table.cases.find((c) => c.id === caseId) || table.cases[0];
  const near = (a) => spacingFits(a, spacing);
  const listed = [...new Set(table.rows.flatMap((r) => (r.spans.get(cs.id) || []).map((x) => x.spacing)))].sort((a, b) => a - b);
  if (!listed.some(near)) return bad('tji.err.spacing', { spacing: depthWord(spacing, table.metric), list: listed.map((x) => depthWord(x, table.metric)).join(', '), table: table.id });
  const cands = [];
  for (const r of table.rows) { const hit = (r.spans.get(cs.id) || []).find((x) => near(x.spacing)); if (hit) cands.push({ r, max: hit.max }); }
  cands.sort((a, b) => a.r.depth - b.r.depth || a.max - b.max);
  const top = cands.reduce((m, c) => Math.max(m, c.max), 0);
  const pick = cands.find((c) => c.max + 1e-6 >= span);
  if (!pick) return bad('tji.err.beyond', { span: (span / FT).toFixed(1) + ' ft / ' + (span / 1000).toFixed(2) + ' m', max: (top / FT).toFixed(1) + ' ft / ' + (top / 1000).toFixed(2) + ' m', table: table.id });
  const label = pick.r.label || (pick.r.series + ' ' + depthWord(pick.r.depth, table.metric));
  return { ok: true, series: pick.r.series, depth: pick.r.depth, size: label, max: pick.max, spacing, caseId: cs.id };
}

Listed maximum span for a size

64-bit

The listed span of a named framing size at a spacing and case.

Looks up the table row by its label and returns the listed maximum in millimeters unchanged, using the same spacing band as the pick.

  • tji-table.js
  • lines 140 to 147
  • BimCraft 0.8.1
/** The listed maximum clear span (mm) of the row a framing named by its size label, at this spacing and case: for the regs add-on's check. null = not in the table. */
export function tjiMaxSpan(table, size, spacing, caseId) {
  if (!table) return null;
  const cs = table.cases.find((c) => c.id === caseId) || table.cases[0];
  const r = table.rows.find((x) => (x.label || (x.series + ' ' + depthWord(x.depth, table.metric))) === size);
  const hit = r && (r.spans.get(cs.id) || []).find((x) => spacingFits(x.spacing, spacing));
  return hit ? { span: hit.max, case: cs } : null;
}

Depth written on the drawing

64-bit

The depth text used in the size label.

in = round(mm ÷ 25.4 × 1000) ÷ 1000

Inches are the millimeter depth divided by 25.4, rounded to 3 decimals with Math.round(x × 1000) ÷ 1000; metric depths are rounded to a whole millimeter. The rounded text goes into the size label.

  • tji-table.js
  • line 117
  • BimCraft 0.8.1
const depthWord = (mm, metric) => (metric ? Math.round(mm) + ' mm' : (Math.round(mm / IN * 1000) / 1000) + ' in');

In detail

Method
Convert the table to millimeters, find the spacing column within a relative band, and take the first depth whose listed span, plus 1e-6 mm, is at least the span asked for.
Number types
64-bit numbers throughout; no typed arrays.
Tolerances
spacingFits accepts a requested spacing between 0.98 and 1.005 times a listed spacing (relative); a listed span passes when max + 1e-6 mm >= span (absolute, millimeters); the error message formats spans with toFixed(1) feet and toFixed(2) meters (text only).
Limits
No interpolation between listed spacings or spans: a span is either under a listed maximum or refused. The label written on a drawing uses a depth rounded to 3 decimals in inches.

Approach: 64-bit, stated limit

The arithmetic is 64-bit, but the method or the data sets the accuracy: a solver tolerance, a mesh, a step size, a datum shift, a table. The limit is stated where the result is shown.

Every policy that takes this approach