Resources Numerical accuracy Calculation policy

Floats read from USD files

Reads numbers from .usda and .usdc files, in the type each value is stored as.

How the precision is kept

Float values are held in 32-bit: the text reader applies Math.fround for float types, arrays of float and half types become Float32Array, and in the binary reader a float array is a view of the file bytes as Float32Array. Double arrays are Float64Array.

Open item. Read double arrays and time values into Float64Array wherever the file holds 64-bit, and keep the 32-bit type only for attributes that are declared float.

Calculations under this policy

5 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/usdc-read.js CadCraft 0.8.2 32-bit in part 3 calculations Reviewed

Tests None found by the review

Inline values

32-bit in part

A value that the file stores inside its 32-bit payload word.

Float, double and timecode share one branch: the payload bits are set into a DataView and read with getFloat32, so the value is 32-bit even for double. Half goes through halfToFloat; ints are the payload as signed or unsigned 32-bit.

  • usd/usdc-read.js
  • lines 305 to 331
  • CadCraft 0.8.2
inline(base, payload) {
  const c = this.c, d = TYPES[base];
  const u = payload >>> 0;
  const s = u | 0;
  switch (base) {
    case 'bool': return !!(u & 0xff);
    case 'uchar': return u & 0xff;
    case 'int': return s;
    case 'uint': return u;
    case 'int64': return s;
    case 'uint64': return u;
    case 'half': return halfToFloat(u & 0xffff);
    case 'float': case 'double': case 'timecode': { F32B.setUint32(0, u, true); return F32B.getFloat32(0, true); }
    case 'string': return c.str(u);
    case 'token': case 'asset': return c.tok(u);
    default: break;
  }
  const i8 = (k) => { const x = (u >>> (8 * k)) & 0xff; return x > 127 ? x - 256 : x; };
  if (d.mat) {
    const m = d.mat, out = new Array(m * m).fill(0);
    for (let k = 0; k < m; k++) out[k * m + k] = i8(k);
    return out;
  }
  const out = new Array(d.n);
  for (let k = 0; k < d.n; k++) out[k] = i8(k);
  return out;
}

Raw arrays

32-bit in part

A packed array of numbers of one type.

A float array is a Float32Array over a copy of the file bytes (no conversion), half is converted to Float32Array through halfToFloat, and double is a Float64Array over the bytes.

  • usd/usdc-read.js
  • lines 401 to 417
  • CadCraft 0.8.2
/** n raw little-endian values of kind k → a typed array (half → Float32Array). */
raw(k, n) {
  const r = this.r;
  const size = { bool: 1, u8: 1, i32: 4, u32: 4, i64: 8, u64: 8, f16: 2, f32: 4, f64: 8 }[k];
  const bytes = r.bytes(n * size).slice();   // a copy: aligned, and free of the file buffer
  switch (k) {
    case 'bool': return Array.from(bytes, (x) => !!x);
    case 'u8': return bytes;
    case 'i32': return new Int32Array(bytes.buffer);
    case 'u32': return new Uint32Array(bytes.buffer);
    case 'i64': return new BigInt64Array(bytes.buffer);
    case 'u64': return new BigUint64Array(bytes.buffer);
    case 'f16': { const h = new Uint16Array(bytes.buffer), out = new Float32Array(n); for (let i = 0; i < n; i++) out[i] = halfToFloat(h[i]); return out; }
    case 'f32': return new Float32Array(bytes.buffer);
    default: return new Float64Array(bytes.buffer);
  }
}

Compressed float arrays

32-bit in part

An array stored as whole numbers or as a lookup table and indexes.

The output array is a Float64Array for double and a Float32Array otherwise; the whole-number form assigns decoded integers into it, and the table form copies table entries, so a float array holds 32-bit values.

  • usd/usdc-read.js
  • lines 372 to 389
  • CadCraft 0.8.2
if (compressed && d.n === 1 && (k === 'f16' || k === 'f32' || k === 'f64') && vcmp(c.ver, [0, 6, 0]) >= 0) {
  if (n < MIN_COMPRESSED) return this.raw(k, n);
  const code = r.u8();
  const out = k === 'f64' ? new Float64Array(n) : new Float32Array(n);
  if (code === 105) {            // 'i': every value a whole number
    const size = r.u64();
    const ints = decodeInts(r.bytes(size), n);
    for (let i = 0; i < n; i++) out[i] = ints[i];
  } else if (code === 116) {     // 't': a lookup table and indices
    const lutN = r.u32();
    if (lutN > n) throw damaged('ccusd.damaged.value');
    const lut = this.raw(k, lutN);
    const size = r.u64();
    const ix = decodeInts(r.bytes(size), n, { unsigned: true });
    for (let i = 0; i < n; i++) { if (ix[i] >= lutN) throw damaged('ccusd.damaged.value'); out[i] = lut[ix[i]]; }
  } else throw damaged('ccusd.damaged.value');
  return out;
}
usd/usda-read.js CadCraft 0.8.2 32-bit in part 2 calculations Reviewed

Tests None found by the review

Float text rounding

32-bit in part

A float or half value read from .usda text.

The parsed 64-bit number is passed through Math.fround for float, and through floatToHalf then halfToFloat for half, so the value is rounded to the stored type when it is read.

  • usd/usda-read.js
  • lines 486 to 487
  • CadCraft 0.8.2
case 'f16': return halfToFloat(floatToHalf(this.number()));
case 'f32': return Math.fround(this.number());

Arrays from text

32-bit in part

A list of parsed numbers as a typed array of the right type.

f32 and f16 lists become Float32Array.from, f64 a Float64Array, and the integer types Int32Array, Uint32Array or Uint8Array; Float32Array.from rounds each number to 32 bits.

  • usd/usda-read.js
  • lines 560 to 571
  • CadCraft 0.8.2
function typedArray(k, items) {
  switch (k) {
    case 'f32': case 'f16': return Float32Array.from(items);
    case 'f64': return Float64Array.from(items);
    case 'i32': return Int32Array.from(items);
    case 'u32': return Uint32Array.from(items);
    case 'u8': return Uint8Array.from(items);
    case 'i64': return BigInt64Array.from(items, (x) => BigInt(x));
    case 'u64': return BigUint64Array.from(items, (x) => BigInt(x));
    default: return items;
  }
}

In detail

Method
Text: parse, then Math.fround or half rounding by type. Binary: decode the inline payload, the raw array, or the compressed integer or lookup-table array.
Number types
Float32Array for f32 and f16 data (half converted with a 65536 entry table), Float64Array for f64; inline values in the binary reader are read as a 32-bit float; integers up to 2^53 stay numbers and larger ones are BigInt.
Tolerances
None.
Limits
In the binary reader an inline float, double or timecode is read from the 32-bit payload with getFloat32, so an inline double carries 32-bit precision. Compressed float arrays that store whole numbers are written into a Float32Array when the type is float, so whole numbers above 2^24 would round.

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