Resources Numerical accuracy Calculation policy

Number text in USD files

Writes numbers into .usda text and decides which attribute types are 32-bit and which are 64-bit.

How the precision is kept

The text of a 64-bit number is the shortest text that reads back to the same double. For float types the value is first rounded to 32-bit (Math.fround) and the shortest text that reads back to that 32-bit value is written, up to 9 digits. Half types are rounded to 16-bit.

Open item. Write 64-bit USD types (double, point3d where allowed) for values the model holds in 64-bit, or note on export that the f types hold about 7 digits.

Calculations under this policy

3 calculations in 2 source files. 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.

usd/usda-write.js CadCraft 0.8.2 32-bit in part 2 calculations Reviewed

Tests None found by the review

Number to text

32-bit in part

The text a number is written as, by type.

f32: text = shortest s with fround(+s) = fround(x)

The double branch uses String(x), which is the shortest text that round-trips the 64-bit value. The float branch calls Math.fround first, so a 64-bit value is rounded to 32-bit before it is written; the half branch rounds to 16-bit. -0 is kept as -0 and NaN and the infinities as nan and inf.

  • usd/usda-write.js
  • lines 229 to 246
  • CadCraft 0.8.2
/** Shortest text that reads back to the same value at its precision. */
export function num(x, k = 'f64') {
  if (Number.isNaN(x)) return 'nan';
  if (x === Infinity) return 'inf';
  if (x === -Infinity) return '-inf';
  if (Object.is(x, -0)) return '-0';
  if (k === 'f32') {
    const f = Math.fround(x);
    for (let p = 1; p <= 9; p++) { const s = shortNum(f, p); if (Math.fround(+s) === f) return s; }
    return String(f);
  }
  if (k === 'f16') {
    const h = floatToHalf(x);
    for (let p = 1; p <= 6; p++) { const s = shortNum(halfToFloat(h), p); if (floatToHalf(+s) === h) return s; }
    return String(halfToFloat(h));
  }
  return String(x).replace('e+', 'e');
}

Shortest digits

64-bit

A number to p significant digits without trailing zeros.

Uses toPrecision(p) on the value and strips trailing zeros and the plus sign of the exponent; the caller tries p from 1 up until the text round-trips, so this only formats.

  • usd/usda-write.js
  • lines 247 to 252
  • CadCraft 0.8.2
function shortNum(x, p) {
  let s = x.toPrecision(p);
  if (s.includes('e')) { const [m, e] = s.split('e'); return (m.includes('.') ? m.replace(/0+$/, '').replace(/\.$/, '') : m) + 'e' + e.replace('+', ''); }
  if (s.includes('.')) s = s.replace(/0+$/, '').replace(/\.$/, '');
  return s;
}
usd/sdf.js CadCraft 0.8.2 Exact 1 calculation Reviewed

Tests None found by the review

Type name to number type

Exact

Which number type a USD type name uses (32-bit float, half or 64-bit double).

A table lookup: the role names point3f, normal3f, vector3f, color3f/4f and texCoord2f/3f map to the float3, float4 or float2 types whose component kind is f32, and the d names map to f64. No arithmetic.

  • usd/sdf.js
  • lines 71 to 80
  • CadCraft 0.8.2
/** 'point3f[]' → { base: 'float3', array: true, role: 'point3f' }, or null for an unknown name. */
export function typeInfo(name) {
  if (typeof name !== 'string') return null;
  const array = name.endsWith('[]');
  const role = array ? name.slice(0, -2) : name;
  const base = ROLES[role] || role;
  if (base === 'opaque') return { base, array, role, def: null };
  const def = TYPES[base];
  return def ? { base, array, role, def } : null;
}

In detail

Method
f64: String(x); f32: increase digits from 1 to 9 until fround of the parsed text equals fround(x); f16: increase from 1 to 6 until the parsed text rounds to the same half.
Number types
64-bit numbers in; Math.fround for float; floatToHalf and halfToFloat for half.
Tolerances
None; the round trip must be exact at the type precision.
Limits
Whether a value is 32-bit is decided by the USD type name: float, float2 to float4, point3f, normal3f, vector3f, color3f, texCoord2f and so on are 32-bit; double and the d types are 64-bit.

Approach: 32-bit step, flagged

One step stores or exchanges 32-bit values that reach a number a person reads. It is listed here with its open item, and the step is either moved to 64-bit or the app will warn where the number is read.

Every policy that takes this approach