Resources Numerical accuracy Calculation policy

Numbers written into the IFC model

Turns the millimeter values the building tools work in into the coordinates, directions, extrusion depths and quantities stored in the IFC file.

How the precision is kept

The tools compute in 64-bit millimeters. On the way into the file every length is divided by the file's length unit and rounded to 6 decimals of that unit, and every direction component is rounded to 12 decimals. Areas and volumes are stored as square and cubic meters rounded to 6 decimals. The rounding, not the arithmetic, sets how many digits a stored value keeps.

Open item. Store coordinates, depths and quantities without the 6-decimal rounding, or round to a fixed number of significant digits instead of a fixed number of decimals.

Calculations under this policy

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

model-write.js BimCraft 0.8.1 64-bit 8 calculations Reviewed

Tests bin/test-bimcraft.mjs, tools/bimcraft-test/mass07-test.mjs

Rounding of a stored value

64-bit

Rounds a number to a given count of decimals before it is written to the file.

round(v, d) = Math.round(v × 10ᵈ) ÷ 10ᵈ, d = 6 by default

The value is multiplied by 10^d, rounded to a whole number and divided again, so the result is the nearest 64-bit number to a d-decimal value; negative zero is returned as 0. Every length, offset and seed point the building tools store passes through it with d = 6, so the stored value keeps 6 decimals of the file unit rather than all its 64-bit digits.

  • model-write.js
  • line 41
  • BimCraft 0.8.1
export const round = (v, d = 6) => { const r = Math.round(v * 10 ** d) / 10 ** d; return Object.is(r, -0) ? 0 : r; };

Placement position and turn

64-bit

The origin and X direction of an element's placement from a point in millimeters and an angle in degrees.

location = round(mm ÷ unit, 6); X direction = (round(cos θ, 12), round(sin θ, 12), 0), θ = degrees × π ÷ 180

The three coordinates are divided by the file unit and rounded to 6 decimals. The angle arrives in degrees and is converted back to radians, then its cosine and sine are rounded to 12 decimals, so a stored direction is within 5e-13 of a unit vector component.

  • model-write.js
  • lines 97 to 102
  • BimCraft 0.8.1
// ---- placements ----
axis3(x, y, z, angDeg) {
  const a = (angDeg || 0) * D2R;
  const loc = this.point3(round(this.u(x)), round(this.u(y)), round(this.u(z)));
  return this.make('IFCAXIS2PLACEMENT3D', this.H(loc), this.H(this.dir(0, 0, 1)), this.H(this.dir(round(Math.cos(a), 12), round(Math.sin(a), 12), 0)));
}

Profile and polyline points

64-bit

A 2D point of a profile, axis or outline as it is stored.

(round(x ÷ unit, 6), round(y ÷ unit, 6))

Each coordinate is divided by the file unit and rounded to 6 decimals. Every wall outline, slab outline and site outline point is written this way.

  • model-write.js
  • lines 112 to 113
  • BimCraft 0.8.1
// ---- geometry (mm in) ----
point2mm(x, y) { return this.point2(round(this.u(x)), round(this.u(y))); }

Extrusion base and depth

64-bit

The start height and depth of a swept solid.

position z = round(z₀ ÷ unit, 6); depth = round(depth ÷ unit, 6)

Base height and depth are each rounded to 6 decimals of the file unit. The direction is written as given, without rounding.

  • model-write.js
  • lines 121 to 124
  • BimCraft 0.8.1
extrude(profile, z0, depth, dir = [0, 0, 1]) {
  const pos = this.make('IFCAXIS2PLACEMENT3D', this.H(this.point3(0, 0, round(this.u(z0)))), null, null);
  return this.make('IFCEXTRUDEDAREASOLID', this.H(profile), this.H(pos), this.H(this.dir(...dir)), round(this.u(depth)));
}

Sloped placement frame

64-bit

A placement with its own Z and X directions, used for sloped parts.

The origin is rounded to 6 decimals of the file unit and each component of the two direction vectors to 12 decimals. The function does not normalize the vectors or check that they are perpendicular; it stores what the caller passes.

  • model-write.js
  • lines 130 to 134
  • BimCraft 0.8.1
/** An IfcAxis2Placement3D at o (mm) with its Z along zDir and X along xDir (unit vectors). */
axisFrame(o, zDir, xDir) {
  const r = (v) => round(v, 12);
  return this.make('IFCAXIS2PLACEMENT3D', this.H(this.point3(round(this.u(o[0])), round(this.u(o[1])), round(this.u(o[2])))), this.H(this.dir(r(zDir[0]), r(zDir[1]), r(zDir[2]))), this.H(this.dir(r(xDir[0]), r(xDir[1]), r(xDir[2]))));
}

Quantity values

64-bit

Converts a length, area or volume in millimeter units to the value stored in a quantity set.

length = round(mm ÷ unit, 6); area = round(mm² ÷ 10⁶, 6); volume = round(mm³ ÷ 10⁹, 6)

Lengths use the file's length unit. Areas are divided by 1e6 and volumes by 1e9, which gives m² and m³ whatever the file's length unit is, and both are rounded to 6 decimals: 1 mm² for an area and 1000 mm³ for a volume. Counts are stored unchanged.

  • model-write.js
  • line 277
  • BimCraft 0.8.1
const conv = (kind, v) => kind === 'length' ? round(this.u(v), 6) : kind === 'area' ? round(v / 1e6, 6) : kind === 'volume' ? round(v / 1e9, 6) : v;   // areas m², volumes m³ (the project's SI units)

World matrix in millimeters

64-bit

Converts a placement matrix from file units to millimeters.

translation × unit in mm; rotation part unchanged

Only the three translation entries are multiplied by the unit factor; one 64-bit multiplication each.

  • model-write.js
  • lines 398 to 403
  • BimCraft 0.8.1
/** A world matrix in file units → mm (translation scaled). */
export function scaleT(M, unitMm) {
  const o = M.slice();
  o[12] *= unitMm; o[13] *= unitMm; o[14] *= unitMm;
  return o;
}

Placement relative to a parent

64-bit

Where an element sits in the axes of its level or site.

M = P⁻¹ × C

Both world matrices are read from the IFC library, scaled to millimeters, and combined with the general 4×4 inverse and product in ifc-util.js. For a model far from the origin the translation of the result is a difference of two large coordinates.

  • model-write.js
  • lines 91 to 95
  • BimCraft 0.8.1
/** A placement's matrix relative to another one's (mm): where `child` sits in `parent`'s axes. */
relMatrix(parentLp, childLp) {
  const P = scaleT(this.api.GetWorldTransformMatrix(this.mid, parentLp), this.unitMm), C = scaleT(this.api.GetWorldTransformMatrix(this.mid, childLp), this.unitMm);
  return mul4(inv4(P), C);
}

In detail

Method
round(v, d) = Math.round(v × 10^d) ÷ 10^d, applied to each coordinate, depth and quantity as it is written; placements relative to a parent come from the product of one inverse world matrix and one world matrix.
Number types
64-bit numbers; matrices are plain arrays of 16 numbers.
Tolerances
No comparisons. Rounding steps: 6 decimals of the file's length unit for lengths, 12 decimals for direction components, 1e-6 m² for areas, 1e-6 m³ for volumes.
Limits
In a file whose length unit is the meter a stored coordinate is on a 0.001 mm grid; in a millimeter file on a 1e-6 mm grid. An area is stored to 1 mm² and a volume to 1000 mm³. The area and volume conversion divides by 1e6 and 1e9 and does not use the file's length unit.

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