Resources Numerical accuracy Calculation policy

Embodied carbon from quantities

Multiplies each item's quantity, with waste, by a carbon factor to give kilograms of CO2 equivalent.

How the precision is kept

The arithmetic is 64-bit: a unit conversion, a multiplication by the factor for each module, and a sum. The result is only as good as the factor table, whose shipped rows are starter values labeled as generic and unchecked. One conversion constant is rounded.

Open item. Write the cubic yard factor as 0.764554857984 (0.9144³ exactly) in place of the rounded 0.764554858.

Calculations under this policy

4 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.

carbon.js TakeoffCraft 0.8.1 64-bit 4 calculations Reviewed

Tests tools/takeoffcraft-test/browser-r3.mjs

Unit conversions to the factor table's basis

64-bit

How many meters, square meters, cubic meters or items one of each takeoff unit is.

CY → m³: × 0.764554858 (exact: 0.9144³ = 0.764554857984)

0.3048, 0.09290304, 0.83612736 (square yard), 9.290304 (roofing square) and 4046.8564224 (acre) are the exact decimal values. The cubic yard is written 0.764554858, rounded from 0.764554857984, so a volume in cubic yards converts 2.1e-11 too high.

  • carbon.js
  • line 27
  • TakeoffCraft 0.8.1
export const BASIS_OF = Object.freeze({ LF: ['m', 0.3048], m: ['m', 1], SF: ['m2', 0.09290304], SY: ['m2', 0.83612736], SQ: ['m2', 9.290304], ac: ['m2', 4046.8564224], 'm²': ['m2', 1], ha: ['m2', 10000], EA: ['item', 1], CY: ['m3', 0.764554858], 'm³': ['m3', 1] });

Quantity in the factor's basis

64-bit

Converts a line's quantity with waste into the unit its carbon factor is given in.

qty = qtyW × factor; kg basis: qtyW × factor × density

One multiplication by the table factor. A wall line priced by area uses wall area × (quantity with waste ÷ quantity) × factor, where a zero quantity is replaced by 1 in the ratio. Mass is volume × factor × density.

  • carbon.js
  • lines 46 to 54
  • TakeoffCraft 0.8.1
/** The quantity of an est row in the row's basis: { qty } or { why: 'unit' | 'noqty' }. */
function inBasis(r, row) {
  const b = BASIS_OF[r.unit];
  if (!b) return { why: 'unit' };
  if (b[0] === row.basis) return { qty: r.qtyW * b[1] };
  if (row.basis === 'm2' && r.wallArea && BASIS_OF[r.wallArea.unit] && BASIS_OF[r.wallArea.unit][0] === 'm2') return { qty: r.wallArea.value * (r.qtyW / (r.qty || 1)) * BASIS_OF[r.wallArea.unit][1], wall: true };   // a wall line with a height
  if (row.basis === 'kg' && b[0] === 'm3' && row.density > 0) return { qty: r.qtyW * b[1] * row.density };
  return { why: 'noqty' };
}

Kilograms per line and the total

64-bit

The carbon of each line for the chosen modules, and their sum.

kg = qty × (A1–A3 + A4 + A5); total = Σ kg

a1a3, a4 and a5 are each quantity × a factor; kg is their sum; the total adds the lines in the estimate's order before the lines are sorted. No rounding.

  • carbon.js
  • lines 56 to 82
  • TakeoffCraft 0.8.1
export function calcCarbon(est, items, carbon) {
  const modules = carbon?.modules?.length ? carbon.modules : ['a1a3'];
  const rows = tableOf(carbon);
  const byId = new Map(rows.map((r) => [r.id, r]));
  const item = new Map((items || []).map((i) => [i.id, i]));
  const lines = [], left = [];
  let total = 0, unchecked = 0;
  for (const r of est.rows) {
    if ((r.kind !== 'item' && r.kind !== 'condition') || r.error || !(r.qtyW > 0)) continue;
    const it = item.get(r.itemId);
    const name = r.kind === 'condition' ? r.name + ' (' + r.type + ')' : r.name;
    const key = r.kind === 'condition' ? r.itemId + '#' + r.cond : r.itemId;
    const pick = carbon?.pick?.[key];
    if (pick === 'none') continue;
    const row = pick && byId.get(pick) ? byId.get(pick) : matchRow(rows, { name: it ? it.name : r.name });
    if (!row) { left.push({ itemId: r.itemId, key, name, why: 'nofactor' }); continue; }
    const q = inBasis(r, row);
    if (q.why) { left.push({ itemId: r.itemId, key, name, why: q.why, need: row.basis, row: row.id }); continue; }
    const a1a3 = q.qty * row.a1a3, a4 = modules.includes('a4') && Number.isFinite(row.a4) ? q.qty * row.a4 : 0, a5 = modules.includes('a5') && Number.isFinite(row.a5) ? q.qty * row.a5 : 0;
    const kg = a1a3 + a4 + a5;
    total += kg;
    if (!row.checked) unchecked++;
    lines.push({ itemId: r.itemId, key, name, unit: r.unit, qty: r.qty, qtyW: r.qtyW, basis: row.basis, row: row.id, rowName: row.match[0], a1a3, a4, a5, kg, auto: !(pick && byId.get(pick)), checked: row.checked });
  }
  lines.sort((a, b) => b.kg - a.kg);
  return { lines, left, total, modules, unchecked, label: STARTER_LABEL };
}

Carbon lines written to a file

64-bit

Writes each line's quantity and kilograms to a CSV file.

written = round(v × 100) ÷ 100

Both numbers are rounded with Math.round(v × 100) ÷ 100 before they are written, so the file carries 2 decimals. Math.round rounds an exact half toward +∞ and has no nudge for values stored just below a half.

  • carbon.js
  • lines 84 to 89
  • TakeoffCraft 0.8.1
export function carbonCsv(res, { head = ['Item', 'Quantity', 'Unit', 'Factor', 'kgCO2e'] } = {}) {
  const n = (v) => String(Math.round(v * 100) / 100);
  const out = [head.map(cell).join(',')];
  for (const l of res.lines) out.push([cell(l.name), n(l.qtyW), cell(l.unit), cell(l.row), n(l.kg)].join(','));
  return out.join('\r\n') + '\r\n';
}

In detail

Method
Quantity in the factor's basis (m, m², m³, item, or kg through a density) × the factor row's A1–A3, A4 and A5 figures for the modules chosen; total = Σ over the lines.
Number types
64-bit numbers.
Limits
The cubic yard to cubic meter factor is written 0.764554858; the exact value is 0.764554857984 (0.9144³), a difference of 2 parts in 10¹¹. The exported file rounds quantities and kilograms to 2 decimals. A factor row is matched to an item by name unless the person picks one.

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