Resources Numerical accuracy Calculation policy

CraftScript values: storage, reading and number text

Holds the numbers a script works with, reads them back from a saved part, and writes the text shown when a statement echoes its result.

How the precision is kept

Numbers are 64-bit and arrays are Float64Array unless a script made an integer or logical array. Saved workspaces are read into the same typed arrays. The echoed text is shortened to 5 significant digits, or 4 decimals in a matrix; the stored value is not changed by it.

Calculations under this policy

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

cs-values.js PartCraft 0.8.1 64-bit 5 calculations Reviewed

Tests bin/test-craftscript.mjs

Number text in the echo

64-bit

The text printed for a number when a statement has no semicolon.

integer below 10¹⁵: all digits; otherwise 5 significant digits

Whole numbers below 10¹⁵ are printed in full. Other values are printed with toPrecision(5), or toExponential(4) when |x| ≥ 10⁵ or |x| < 10⁻⁴. This is the text a person reads in the console; the variable keeps its 64-bit value.

  • cs-values.js
  • lines 321 to 330
  • PartCraft 0.8.1
/** A number as MATLAB's "format short g"-ish text. */
export function fmtNum(x) {
  if (Number.isNaN(x)) return 'NaN';
  if (x === Infinity) return 'Inf';
  if (x === -Infinity) return '-Inf';
  if (Number.isInteger(x) && Math.abs(x) < 1e15) return String(x);
  const a = Math.abs(x);
  if (a >= 1e5 || a < 1e-4) return x.toExponential(4).replace(/e([+-])(\d)$/, 'e$10$2');
  return String(+x.toPrecision(5));
}

Matrix text in the echo

64-bit

The text printed for a vector or matrix.

An all-integer matrix is printed in full. Otherwise every entry is printed with 4 decimals, or with toExponential(4) when the largest magnitude is 10⁵ or more, or below 10⁻³. A small entry beside a large one can therefore print as 0.0000. At most 60 rows and 30 columns are shown.

  • cs-values.js
  • lines 376 to 403
  • PartCraft 0.8.1
function tensorText(v) {
  const n = v.data.length;
  if (n === 0) return '     []' + (v.shape.some((s) => s) ? `  (${v.shape.join('×')})` : '');
  if (n === 1) return fmtNum(v.data[0]);
  if (v.shape.length > 2) {
    const [r, c] = v.shape, page = r * c, pages = n / page, out = [];
    for (let p = 0; p < Math.min(pages, 8); p++) {
      const idx = []; let q = p;
      for (let d = 2; d < v.shape.length; d++) { idx.push(q % v.shape[d] + 1); q = Math.floor(q / v.shape[d]); }
      out.push(`  (:,:,${idx.join(',')}) =`, tensorText({ t: 'tensor', shape: [r, c], data: v.data.subarray(p * page, (p + 1) * page) }));
    }
    if (pages > 8) out.push('  ' + t('script.val.show.more_pages', { n: pages - 8 }));
    return out.join('\n');
  }
  const [r, c] = v.shape;
  const R = Math.min(r, 60), C = Math.min(c, 30);
  let allInt = true, big = 0;
  for (let i = 0; i < n; i++) { const x = v.data[i]; if (Number.isFinite(x)) { if (!Number.isInteger(x)) allInt = false; big = Math.max(big, Math.abs(x)); } }
  const useE = !allInt && (big >= 1e5 || (big > 0 && big < 1e-3));
  const f = (x) => (!Number.isFinite(x) ? fmtNum(x) : allInt ? String(x) : useE ? x.toExponential(4) : x.toFixed(4));
  const cells = [];
  let w = 1;
  for (let i = 0; i < R; i++) { const row = []; for (let j = 0; j < C; j++) { const s = f(v.data[i + j * r]); w = Math.max(w, s.length); row.push(s); } cells.push(row); }
  const lines = cells.map((row) => row.map((s) => s.padStart(w + 3)).join(''));
  if (c > C) lines[0] += '   ' + t('script.val.show.more_cols', { n: c });
  if (r > R) lines.push('   ' + t('script.val.show.more_rows', { n: r }));
  return lines.join('\n');
}

Arrays read from a saved workspace

64-bit

Checks and copies a numeric array from a part file or a worker message.

A plain list is converted with Number into the array type its dtype names: Float64Array by default, Int32Array for i32, Uint8Array for u8. A typed array must already be of that type and is copied. The element count must equal the product of the shape.

  • cs-values.js
  • lines 217 to 231
  • PartCraft 0.8.1
const arr = (a, Ctor, n, what) => {
  let x = a;
  if (Array.isArray(a)) { if (a.length > CAPS.tensor) throw new CsError(t('script.val.read.too_many', { what })); x = Ctor.from(a, Number); }
  if (!(x instanceof Ctor)) throw new CsError(t('script.val.read.wrong_type', { what }));
  if (n !== undefined && x.length !== n) throw new CsError(t('script.val.read.count', { what, got: x.length, want: n }));
  return x.slice();
};
switch (raw.t) {
  case 'tensor': {
    if (!Array.isArray(raw.shape) || raw.shape.length < 1 || raw.shape.length > CAPS.dims || !raw.shape.every((s) => Number.isInteger(s) && s >= 0)) throw new CsError(t('script.val.read.array_shape'));
    const n = prodOf(raw.shape);
    checkSize(n);
    const dt = raw.dtype === 'u8' ? 'u8' : raw.dtype === 'i32' ? 'i32' : 'f64';
    return tensor(raw.shape, arr(raw.data, TYPED[dt], n, t('script.val.what.array')));
  }

Minimum and maximum of a variable

64-bit

The range shown for each variable in the Variables panel.

One pass of 64-bit comparisons over the data; a NaN entry fails both comparisons and is skipped. For a sparse matrix with unstored entries, 0 is included in the range.

  • cs-values.js
  • lines 159 to 170
  • PartCraft 0.8.1
/** Summary for the Variables panel: { type, size: [..], bytes, min?, max? }. */
export function summary(v) {
  const s = { type: typeOf(v), size: sizeOf(v), bytes: sizeBytes(v) };
  const d = typeof v === 'number' ? [v] : isTensor(v) ? v.data : v && v.t === 'sparse' ? v.val : null;
  if (d && d.length) {
    let lo = Infinity, hi = -Infinity;
    for (let i = 0; i < d.length; i++) { const x = d[i]; if (x < lo) lo = x; if (x > hi) hi = x; }
    if (v && v.t === 'sparse' && v.val.length < v.n * v.m) { lo = Math.min(lo, 0); hi = Math.max(hi, 0); }
    s.min = lo; s.max = hi;
  }
  return s;
}

Numeric data as 64-bit

64-bit

Gives builtins a Float64Array of any numeric value.

Returns the tensor's own Float64Array, or converts Int32Array and Uint8Array data with Float64Array.from, which is exact for those integer types.

  • cs-values.js
  • lines 110 to 114
  • PartCraft 0.8.1
/** Float64Array of a numeric value's elements (column-major). May return the tensor's own array: do not write to it. */
export function f64(v) {
  const t = asTensor(v);
  return t.data instanceof Float64Array ? t.data : Float64Array.from(t.data);
}

In detail

Method
Values are plain data: numbers, tensors in column-major order, sparse matrices in compressed rows. Echo text follows a short format: toPrecision(5) or toExponential(4) for a number, toFixed(4) for a matrix.
Number types
64-bit numbers; Float64Array, Int32Array (i32) and Uint8Array (logical) inside tensors; Float64Array values in sparse matrices.
Limits
The echo shows 5 significant digits of a non-integer. A script has to format a number itself to see all of its digits.

Approach: 64-bit, direct

Every value is a 64-bit number from input to result. Formulas are closed form or a direct solve, and every comparison uses a stated tolerance.

Every policy that takes this approach