Resources Numerical accuracy Calculation policy

IFC export: numbers written to the file

Writes the whole project as an IFC file: coordinates, alignment parameters, lengths, areas, volumes and unit factors as text.

How the precision is kept

Values are computed in 64-bit and then written as decimal text with a fixed number of decimal places: 6 for coordinates, lengths, areas, volumes and other quantities, 9 for ratios, speeds and spiral constants, 12 for directions and gradients, 15 for polynomial coefficients and 16 for the unit factor. Coordinates are written relative to a file origin rounded to 100 units, so 6 decimals is one millionth of a file unit. The file therefore carries fewer digits than the project holds.

Open item. Write coordinates, alignment parameters and quantities with enough significant digits to read back the same 64-bit number (up to 17) instead of a fixed number of decimal places.

Calculations under this policy

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

ifc-civil.js CivilCraft 0.8.1 64-bit 25 calculations Reviewed

Tests bin/test-civilcraft-ifc.mjs

A real number as IFC text

64-bit

Formats every number the export writes.

text = v.toFixed(d) with trailing zeros removed; d = 6 unless stated

The value is rounded to d decimal places, 6 by default, so the file holds an absolute resolution of 1e-6 rather than 15 significant digits. A value below half of the last place is written as 0, and a value that is not finite is written as 0.

  • ifc-civil.js
  • line 58
  • CivilCraft 0.8.1
const R = (v, d = 6) => { if (!fin(v)) return '0.'; let s = (Math.abs(v) < 0.5 * 10 ** -d ? 0 : v).toFixed(d).replace(/(\.\d*?)0+$/, '$1'); if (!s.includes('.')) s += '.'; return s === '-0.' ? '0.' : s; };

Points and directions

64-bit

Writes a Cartesian point and a direction.

Point coordinates are written with 6 decimals and direction ratios with 12. Identical text is written once and shared.

  • ifc-civil.js
  • lines 117 to 118
  • CivilCraft 0.8.1
pt(p) { return this.one('IfcCartesianPoint', { Coordinates: L(p.map((v) => R(v))) }); }
dir(d) { return this.one('IfcDirection', { DirectionRatios: L(d.map((v) => R(v, 12))) }); }

File origin

Exact

The local origin that coordinates are written from: the middle of the content rounded to 100 units.

origin = round((min + max) ÷ 200) × 100

The origin is a whole multiple of 100, so subtracting it from a coordinate removes the large part without adding a fraction. Elevations are not shifted (z origin 0).

  • ifc-civil.js
  • lines 131 to 144
  • CivilCraft 0.8.1
/** The middle of everything, rounded to 100 units (z stays 0: elevations are written as they are). */
export function fileOrigin(m) {
  let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
  const see = (x, y) => { if (fin(x) && fin(y)) { x0 = Math.min(x0, x); y0 = Math.min(y0, y); x1 = Math.max(x1, x); y1 = Math.max(y1, y); } };
  for (const a of m.alignments) for (const q of a.poly) see(q.x, q.y);
  for (const s of m.surfaces) for (const q of s.pts) see(q.x, q.y);
  for (const p of m.pads) for (let i = 0; i < (p.top?.pos.length || 0); i += 3) see(p.top.pos[i], p.top.pos[i + 1]);
  for (const s of m.network?.structures || []) see(s.x, s.y);
  for (const r of m.parcels) for (const q of r.pts) see(q.x, q.y);
  for (const e of m.landscape) if (e.at) see(e.at.x, e.at.y);
  for (const ms of m.masses) for (const q of ms.pts) see(q.x, q.y);
  if (!fin(x0)) return { x: 0, y: 0, z: 0 };
  return { x: Math.round((x0 + x1) / 200) * 100, y: Math.round((y0 + y1) / 200) * 100, z: 0 };
}

Unit factor to SI

Exact

Meters per file unit, and the conversion of areas and volumes to square and cubic meters.

um = 1, 0.3048 or 1200 ÷ 3937; m² = a × um²; m³ = v × um³

The international foot 0.3048 is exact and the US survey foot is formed as the fraction 1200 ÷ 3937 (FT_US_M in ifc-massing.js). Area and volume factors are built by repeated multiplication by um.

  • ifc-civil.js
  • line 151
  • CivilCraft 0.8.1
const unit = fileUnit(units, crs), um = unit === 'm' ? 1 : unit === 'ft' ? 0.3048 : FT_US_M, m2 = (a) => a * um * um, m3 = (v) => v * um * um * um;

Conversion factor in the file

64-bit

Writes the foot's length in meters into the file's unit definition.

The factor is written with 16 decimals. For 1200 ÷ 3937 that gives 0.3048006096012192, which reads back as the same 64-bit number; 0.3048 is written as 0.3048.

  • ifc-civil.js
  • line 176
  • CivilCraft 0.8.1
const mwu = w.ent('IfcMeasureWithUnit', { ValueComponent: 'IFCLENGTHMEASURE(' + R(um, 16) + ')', UnitComponent: metre });

Property values and quantities

64-bit

Writes lengths, areas, volumes, ratios, speeds and counts in property sets and quantity sets.

Lengths, areas, volumes, counts and weights are written with 6 decimals; ratios and speeds with 9. Areas and volumes are converted to SI first and rounded after. A positive length below 1e-6 is written as 1e-6, and integer and count values are rounded to whole numbers.

  • ifc-civil.js
  • lines 184 to 197
  • CivilCraft 0.8.1
const T = {
  len: (v) => 'IFCLENGTHMEASURE(' + R(v) + ')', pos: (v) => 'IFCPOSITIVELENGTHMEASURE(' + R(Math.max(v, 1e-6)) + ')', area: (v) => 'IFCAREAMEASURE(' + R(v) + ')',
  vol: (v) => 'IFCVOLUMEMEASURE(' + R(v) + ')', ratio: (v) => 'IFCRATIOMEASURE(' + R(v, 9) + ')', label: (s) => 'IFCLABEL(' + S(String(s).slice(0, 255)) + ')', text: (s) => 'IFCTEXT(' + S(s) + ')',
  bool: (v) => 'IFCBOOLEAN(' + B(v) + ')', speed: (v) => 'IFCLINEARVELOCITYMEASURE(' + R(v, 9) + ')', int: (v) => 'IFCINTEGER(' + Math.round(v) + ')', count: (v) => 'IFCCOUNTMEASURE(' + Math.round(v) + ')', real: (v) => 'IFCREAL(' + R(v) + ')',
};
const pset = (objs, name, props, tag) => {
  const ps = props.filter(([, v]) => v != null && v !== '' && !(typeof v === 'number' && !fin(v))).map(([k, v, f]) => w.ent('IfcPropertySingleValue', { Name: S(k), NominalValue: T[f](v) }));
  if (!ps.length || !objs.length) return null;
  const set = root('IfcPropertySet', 'pset|' + tag + '|' + name, { Name: S(name), HasProperties: L(ps) });
  counts.psets++;
  root('IfcRelDefinesByProperties', 'rdp|' + tag + '|' + name, { RelatedObjects: L(objs), RelatingPropertyDefinition: set });
  return set;
};
const QK = { len: ['IfcQuantityLength', 'LengthValue', (v) => R(v)], area: ['IfcQuantityArea', 'AreaValue', (v) => R(m2(v))], vol: ['IfcQuantityVolume', 'VolumeValue', (v) => R(m3(v))], count: ['IfcQuantityCount', 'CountValue', (v) => R(v)], kg: ['IfcQuantityWeight', 'WeightValue', (v) => R(v)] };

Surface area written with a mesh

64-bit

The area of a surface, corridor part or slope in square meters.

area = Σ |(b − a) × (c − a)| ÷ 2 × um²

Triangle areas from differences of coordinates, added in a plain running sum, then multiplied by the squared unit factor.

  • ifc-civil.js
  • line 152
  • CivilCraft 0.8.1
const meshArea = (m) => { let s = 0; for (let i = 0; i < m.idx.length; i += 3) s += triArea(m.pos, m.idx[i], m.idx[i + 1], m.idx[i + 2]); return m2(s); };

Triangle meshes

64-bit

Writes a surface or solid as a point list and triangles.

x′ = x − origin_x, written with 6 decimals

Each vertex has the file origin subtracted and is then rounded to 6 decimals. Two vertices closer than that can be written as the same coordinates; the triangle indexes are not changed.

  • ifc-civil.js
  • lines 210 to 228
  • CivilCraft 0.8.1
const tess = (mesh, { tin = false } = {}) => {
  const P = mesh.pos, I = mesh.idx, n = P.length / 3;
  counts.triangles += I.length / 3;
  if (!X2) {
    const pts = [];
    for (let i = 0; i < n; i++) pts.push('(' + R(lx(P[i * 3])) + ',' + R(ly(P[i * 3 + 1])) + ',' + R(lz(P[i * 3 + 2])) + ')');
    const tris = [];
    for (let i = 0; i < I.length; i += 3) tris.push('(' + (I[i] + 1) + ',' + (I[i + 1] + 1) + ',' + (I[i + 2] + 1) + ')');
    const coords = w.ent('IfcCartesianPointList3D', { CoordList: L(pts) });
    if (tin && X4) return ['Tessellation', w.ent('IfcTriangulatedIrregularNetwork', { Coordinates: coords, Closed: '.F.', CoordIndex: L(tris), Flags: L(tris.map(() => '0')) })];
    return ['Tessellation', w.ent('IfcTriangulatedFaceSet', { Coordinates: coords, Closed: B(!!mesh.closed), CoordIndex: L(tris) })];
  }
  const pts = [];
  for (let i = 0; i < n; i++) pts.push(w.ent('IfcCartesianPoint', { Coordinates: L([R(lx(P[i * 3])), R(ly(P[i * 3 + 1])), R(lz(P[i * 3 + 2]))]) }));
  const faces = [];
  for (let i = 0; i < I.length; i += 3) faces.push(w.ent('IfcFace', { Bounds: L([w.ent('IfcFaceOuterBound', { Bound: w.ent('IfcPolyLoop', { Polygon: L([pts[I[i]], pts[I[i + 1]], pts[I[i + 2]]]) }), Orientation: '.T.' })]) }));
  if (mesh.closed) return ['Brep', w.ent('IfcFacetedBrep', { Outer: w.ent('IfcClosedShell', { CfsFaces: L(faces) }) })];
  return ['SurfaceModel', w.ent('IfcShellBasedSurfaceModel', { SbsmBoundary: L([w.ent('IfcOpenShell', { CfsFaces: L(faces) })]) })];
};

Site latitude and longitude

64-bit

Writes a latitude or longitude as degrees, minutes, seconds and millionths of a second.

Degrees, minutes and seconds are taken with Math.floor and the remainder is rounded to a millionth of an arc second, the resolution of the IFC compound angle. The remainder is found by repeated subtraction and multiplication by 60.

  • ifc-civil.js
  • line 246
  • CivilCraft 0.8.1
const dms = (deg) => { const sg = deg < 0 ? -1 : 1, a = Math.abs(deg), d = Math.floor(a), mm = Math.floor((a - d) * 60), sc = ((a - d) * 60 - mm) * 60, s = Math.floor(sc), u = Math.round((sc - s) * 1e6); return L([sg * d, sg * mm, sg * s, sg * u]); };

Map conversion

64-bit

The grid easting and northing of the file origin, and the rotation and scale of a local ground system.

E₀ = to_x + s·(cos r·Δx − sin r·Δy); N₀ = to_y + s·(sin r·Δx + cos r·Δy)

Eastings, northings and height are written with 6 decimals; the rotation as cos and sin with 12 decimals and the scale with 12. Without a local system the easting and northing are the whole-number origin itself.

  • ifc-civil.js
  • lines 254 to 260
  • CivilCraft 0.8.1
const E0 = l ? l.to[0] + sc * (Math.cos(rot) * (o.x - l.from[0]) - Math.sin(rot) * (o.y - l.from[1])) : o.x;
const N0 = l ? l.to[1] + sc * (Math.sin(rot) * (o.x - l.from[0]) + Math.cos(rot) * (o.y - l.from[1])) : o.y;
const datum = /\+datum=([^\s+]+)/.exec(crs.proj4 || '')?.[1] || /\+ellps=([^\s+]+)/.exec(crs.proj4 || '')?.[1] || crs.name || 'unknown';   // i18n-ignore: IFC file data
geo = { name: crs.code ? 'EPSG:' + crs.code : crs.name || 'CRS', desc: crs.name || '', datum, E0, N0, H: o.z, xa: Math.cos(rot), xo: Math.sin(rot), scale: sc };
if (!X2) {
  const crsId = w.ent('IfcProjectedCRS', { Name: S(geo.name), Description: S(geo.desc), GeodeticDatum: S(geo.datum), MapUnit: length });
  w.ent('IfcMapConversion', { SourceCRS: ctx, TargetCRS: crsId, Eastings: R(E0), Northings: R(N0), OrthogonalHeight: R(o.z), XAxisAbscissa: R(geo.xa, 12), XAxisOrdinate: R(geo.xo, 12), Scale: R(sc, 12) });

PI and PVI tables as text

64-bit

Writes each alignment's PIs and PVIs into a property set, which is what CivilCraft reads back from IFC4 and IFC2x3 files.

x, y, radius, spiral lengths, stations, elevations and curve lengths are rounded to 6 decimals with toFixed(6), and cant to 3 decimals. The PI coordinates here are absolute project coordinates, not shifted to the file origin, so with coordinates in the millions 6 decimals is 13 significant digits.

  • ifc-civil.js
  • lines 333 to 334
  • CivilCraft 0.8.1
['PIs', a.pis.map((p) => [...[p.x, p.y, p.r || 0, p.ls1 || p.ls || 0, p.ls2 || p.ls || 0].map((v) => +(+v).toFixed(6)), ...(p.tr || p.cant ? [p.tr || '', +(+(p.cant || 0)).toFixed(3)] : [])].join(',')).join(';'), 'text'],   // + transition kind and cant (mm): 71.26 / B
['PVIs', a.pvis.map((p) => [p.sta, p.z, p.L || 0].map((v) => +(+v).toFixed(6)).join(',')).join(';'), 'text'],

Continuity between horizontal segments

64-bit

Decides whether two neighboring segments are written as continuous, same direction, or same direction and curvature.

Directions count as equal within 1e-7 rad, after the difference is wrapped through atan2(sin, cos). Curvatures count as equal within 1e-9 (absolute, per file unit), and an end curvature below 1e-12 counts as straight.

  • ifc-civil.js
  • lines 359 to 363
  • CivilCraft 0.8.1
const sameDir = (d1, d2) => Math.abs(Math.atan2(Math.sin(d1 - d2), Math.cos(d1 - d2))) < 1e-7;
let trans;
if (!nx) trans = Math.abs(kEnd) < 1e-12 ? '.CONTSAMEGRADIENTSAMECURVATURE.' : '.CONTSAMEGRADIENT.';
else if (!sameDir(dirEnd, nx.dir)) trans = '.CONTINUOUS.';
else trans = Math.abs(kEnd - kNext) < 1e-9 ? '.CONTSAMEGRADIENTSAMECURVATURE.' : '.CONTSAMEGRADIENT.';

Spiral terms

64-bit

The constants of Bloss, cosine, sine and Helmert transition curves.

A_i = L × |a_i|^(−1/n) × sign a_i; f = L ÷ R₁ − L ÷ R₀; a₀ = L ÷ R₀

Each term is one 64-bit expression written with 9 decimals. f is a difference of two curvature terms; for a spiral between a straight and a curve one of them is zero, so nothing cancels.

  • ifc-civil.js
  • lines 371 to 372
  • CivilCraft 0.8.1
const term = (a, n) => (a ? R(s.len * Math.abs(a) ** (-1 / n) * Math.sign(a), 9) : null);
const fK = s.len * curv(s.r1) - s.len * curv(s.r0), a0 = s.len * curv(s.r0);

Clothoid constant

64-bit

The constant A of a clothoid transition.

A = L ÷ √|f| × sign f

One 64-bit expression, written with 9 decimals.

  • ifc-civil.js
  • lines 394 to 395
  • CivilCraft 0.8.1
const A = (s.len / Math.sqrt(Math.abs(fK))) * Math.sign(fK);
parent = w.ent('IfcClothoid', { Position: ax2d0, ClothoidConstant: R(A, 9) });

Cubic parabola coefficient

64-bit

The coefficient of y = x³ ÷ (6·R·Lp) for a cubic transition.

A₃ = 1 ÷ (6 × R × Lp)

The coefficient is written with 15 decimal places, which is an absolute resolution on a very small number: for R = 1000 and Lp = 100 the text is 0.000001666666667, 10 significant digits, and fewer for larger curves.

  • ifc-civil.js
  • lines 379 to 380
  • CivilCraft 0.8.1
const A3 = s.r1 ? 1 / (6 * s.r1 * s.Lp) : -1 / (6 * s.r0 * s.Lp);
parent = w.ent('IfcPolynomialCurve', { Position: ax2d0, CoefficientsX: L(['0.', '1.']), CoefficientsY: L(['0.', '0.', '0.', R(A3, 15)]) });

Horizontal segment parameters

64-bit

Writes a segment's start point, start direction, radii and length.

The start direction is wrapped to −π…π through atan2(sin, cos) and written with 12 decimals; the start point, radii and length with 6.

  • ifc-civil.js
  • line 400
  • CivilCraft 0.8.1
const dp = w.ent('IfcAlignmentHorizontalSegment', { StartPoint: w.pt([lx(s.x), ly(s.y)]), StartDirection: R(Math.atan2(Math.sin(s.dir), Math.cos(s.dir)), 12), StartRadiusOfCurvature: R(s.r0), EndRadiusOfCurvature: R(s.r1), SegmentLength: R(s.len), PredefinedType: E(s.type) });

Vertical segment parameters and curve

64-bit

Writes each grade or vertical curve: start distance, height, gradients, and the curve as a line or a polynomial.

C = (g₁ − g₀) ÷ (2L); line length = L ÷ cos(atan g₀); curve length = parabola length

Heights and lengths are written with 6 decimals, gradients with 12 and the parabola coefficient C with 15 decimal places (absolute, so a flat curve keeps fewer significant digits). The placement direction is cos and sin of atan(g₀).

  • ifc-civil.js
  • lines 418 to 423
  • CivilCraft 0.8.1
const th = Math.atan(s.g0), plc = w.ent('IfcAxis2Placement2D', { Location: w.pt([d0, s.z0]), RefDirection: w.dir([Math.cos(th), Math.sin(th)]) });
let parent, len;
if (s.type === 'CONSTANTGRADIENT') { parent = w.one('IfcLine', { Pnt: O2, Dir: w.one('IfcVector', { Orientation: X2D, Magnitude: '1.' }) }); len = s.len / Math.cos(th); }
else { const C = (s.g1 - s.g0) / (2 * s.len); parent = w.ent('IfcPolynomialCurve', { Position: ax2d0, CoefficientsX: L(['0.', '1.']), CoefficientsY: L([R(s.z0), R(s.g0, 12), R(C, 15)]) }); len = parabolaLength(s.g0, C, s.len); }
vCurve.push(w.ent('IfcCurveSegment', { Transition: trans, Placement: plc, SegmentStart: 'IFCLENGTHMEASURE(0.)', SegmentLength: 'IFCLENGTHMEASURE(' + R(len) + ')', ParentCurve: parent }));
vSegs.push(root('IfcAlignmentSegment', tag + '|v|' + i, { DesignParameters: w.ent('IfcAlignmentVerticalSegment', { StartDistAlong: R(d0), HorizontalLength: R(s.len), StartHeight: R(s.z0), StartGradient: R(s.g0, 12), EndGradient: R(s.g1, 12), PredefinedType: E(s.type) }) }));

Length along a parabola

64-bit

The true length of a vertical curve y = A + Bx + Cx² over its horizontal length.

F(x) = (d·√(1 + d²) + asinh d) ÷ 4C, d = B + 2Cx; length = F(L) − F(0)

Closed form. The two F values are each divided by 4C and then subtracted, so for a small C they are large and nearly equal and the difference loses digits; below |C| = 1e-15 the straight-line formula is used instead.

  • ifc-civil.js
  • lines 125 to 130
  • CivilCraft 0.8.1
/** Length of y = A + Bx + Cx² over 0 … L (closed form; a straight line when C = 0). */
export function parabolaLength(B, C, Lh) {
  if (Math.abs(C) < 1e-15) return Lh * Math.hypot(1, B);
  const F = (x) => { const d = B + 2 * C * x, r = Math.hypot(1, d); return (d * r + Math.asinh(d)) / (4 * C); };
  return F(Lh) - F(0);
}

Cant axis

64-bit

The leaning axis of a cant segment from the height difference of the two rails.

dy = 2 × half difference; dz = √(max(0, base² − dy²)); axis = (0, dy ÷ base, dz ÷ base)

The square root argument is clamped at zero. Cant values in millimeters are converted to file units by ÷ 1000 ÷ um.

  • ifc-civil.js
  • line 285
  • CivilCraft 0.8.1
const axis = (half) => { const dy = 2 * half, dz = Math.sqrt(Math.max(0, base * base - dy * dy)); return w.dir([0, dy / base, dz / base]); };

Helmert cant halves

64-bit

The two second-order spirals of a Helmert cant transition and the slope where they meet.

slope = (L ÷ 2)² × (2·(L ÷ 2) ÷ A₂³)

The slope at the middle is computed from +A2a, the quadratic term parsed back from its text already rounded to 9 decimals, not from the unrounded value.

  • ifc-civil.js
  • lines 305 to 307
  • CivilCraft 0.8.1
const Ss = sl + sr, Se = el + er, F2 = Se - Ss, A2a = T(4 * F2, 3, 4 / 3);
seg(0, Lh / 2, w.ent('IfcSecondOrderPolynomialSpiral', { Position: P2(0, 0), QuadraticTerm: A2a, ConstantTerm: T(2 * Ss, 1, 2) }), [d0, Ss / 2, 0]);
const slope = (Lh / 2) ** 2 * ((2 * (Lh / 2)) / (+A2a) ** 3);

Sleeper placement axes

64-bit

The three axes of each sleeper from its direction across the track and the cross slope.

A₁ = (across_x, across_y, slope) ÷ |…|; A₂ = (−across_y, across_x, 0); A₃ = A₁ × A₂

The first axis is normalized with Math.hypot and the third is a cross product; each is written with 12 decimals.

  • ifc-civil.js
  • lines 556 to 557
  • CivilCraft 0.8.1
const sl = q.slope, a1 = [q.across.x, q.across.y, sl], n1 = Math.hypot(...a1), A1 = a1.map((v) => v / n1), A2 = [-q.across.y, q.across.x, 0];
const A3 = [A1[1] * A2[2] - A1[2] * A2[1], A1[2] * A2[0] - A1[0] * A2[2], A1[0] * A2[1] - A1[1] * A2[0]];

Pipe solid

64-bit

A pipe as a circle swept from the upstream to the downstream invert.

L₃ = hypot(Δx, Δy, Δz); axis = Δ ÷ L₃; start z = invert + D ÷ 2

The axis is the 3D difference divided by its length; pipes shorter than 1e-9 are skipped. The reference direction divides by the plan length of the axis, or by 1 when the pipe is vertical. The swept depth is raised to at least 1e-6.

  • ifc-civil.js
  • lines 686 to 689
  • CivilCraft 0.8.1
const r = p.dia / 2, dx = p.b.x - p.a.x, dy = p.b.y - p.a.y, dz = p.b.z - p.a.z, L3 = Math.hypot(dx, dy, dz);
if (!(L3 > 1e-9)) continue;
const u = [dx / L3, dy / L3, dz / L3], hl = Math.hypot(u[0], u[1]) || 1, ref = [-u[1] / hl, u[0] / hl, 0];
const solid = extrude(w.ent('IfcCircleProfileDef', { ProfileType: '.AREA.', Position: ax2d0, Radius: R(r) }), w.ent('IfcAxis2Placement3D', { Location: w.pt([lx(p.a.x), ly(p.a.y), lz(p.a.z + r)]), Axis: w.dir(u), RefDirection: w.dir(ref) }), L3);

Rail weight

64-bit

The weight of each rail from its length and section.

weight = length × um × kg per meter

The length in file units is converted to meters by the unit factor and multiplied by the section's mass per meter; the result is written with 6 decimals.

  • ifc-civil.js
  • line 545
  • CivilCraft 0.8.1
qto([el], 'Qto_RailBaseQuantities', [['Length', 'len', r.length], ['Weight', 'kg', r.length * um * r.kg]], rtag);

Design speed in meters per second

Exact

Converts a design speed in km/h or mph for the road design property.

m/s = km/h ÷ 3.6; m/s = mph × 0.44704

0.44704 is the exact meters per second in a mile per hour; the km/h factor is 1 ÷ 3.6 formed by division. The converted speed is written with 9 decimals.

  • ifc-civil.js
  • line 272
  • CivilCraft 0.8.1
const MPS = { 'km/h': 1 / 3.6, mph: 0.44704 };

Building mass footprint and height

64-bit

The footprint area, height and gross floor area written for each building mass.

footprint = |Σ (x_i·y_j − x_j·y_i)| ÷ 2; height = floors × floor height

The shoelace sum uses absolute project coordinates with no shift to a local origin. The sign of the sum decides whether the outline is reversed before it is written.

  • ifc-civil.js
  • lines 813 to 816
  • CivilCraft 0.8.1
let P = m.pts, a = 0;
for (let i = 0; i < P.length; i++) { const p = P[i], q = P[(i + 1) % P.length]; a += p.x * q.y - q.x * p.y; }
if (a < 0) P = P.slice().reverse();
const Hm = m.floors * m.floorHeight, fp = Math.abs(a / 2);

In detail

Method
ISO 10303-21 text; each real through toFixed(d) with trailing zeros removed. Areas and volumes converted to SI with the unit factor before rounding.
Number types
64-bit numbers, written as text with 6 to 16 decimal places.
Tolerances
Values smaller than half a unit in the last written place become 0. Positive lengths are raised to at least 1e-6. Same direction between segments: 1e-7 rad. Same curvature: 1e-9. Parabola treated as a line when |C| < 1e-15. Pipes shorter than 1e-9 are left out.
Limits
A fixed number of decimals is an absolute resolution: small values such as a cubic coefficient keep few significant digits. The PI and PVI table that CivilCraft reads back from IFC4 and IFC2x3 files is rounded to 6 decimals. Latitude and longitude are written to a millionth of an arc second, which is the IFC format's own resolution. The geometric context states a precision of 1e-5.

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