Endianness

A number wider than one byte has to land in memory in some order — little-endian machines write the small end first, network and file formats often write the big end first. JavaScript hides this until binary data crosses a boundary: typed arrays speak native order for fast GPU uploads, DataView parses files and sockets with the order spelled out.

Core Idea

The wreck above is not corruption: both views read the same bytes, and the only difference is one boolean on getFloat32. Flip which end of each number is read first and the exponent bits land in the wrong place — a clean mesh becomes values spanning ±10³⁸. 0x12345678 is one number but four bytes, and memory is addressed one byte at a time — so the bytes have to land in some order. Endianness is that order. It is invisible for almost all programming: your code, your CPU, and your typed arrays all agree, so nothing ever looks flipped. It turns real the moment binary data crosses a system boundary — a file parsed in the browser, a binary WebSocket message, a mesh downloaded from a server — because then the machine that wrote the bytes and the machine reading them are free to disagree.

The Two Orders

Order 0x12345678 in memory (low → high address)
Little-endian 78 56 34 12
Big-endian 12 34 56 78

Neither is wrong — they’re conventions. The failure mode is mixing them, and it fails loudly in the worst way: no error, just a wrong number. Lay a value into memory below and read it back with the wrong flag:

A float doesn’t survive the flip any better than an integer — 1.0 read backwards isn’t -1.0 or 2.0, it’s 4.6006e-41, because the sign, exponent, and mantissa bits all land in the wrong fields. That “almost zero but not quite” garbage is the classic symptom of an endianness bug in mesh or texture parsing.

What JavaScript Does About It

The typed-array API splits the problem deliberately in two:

Float32Array, Uint16Array, … always use native CPU order. No flag, no conversion, no per-element cost — which is exactly what makes them the fast path for GPU work. By the time a buffer reaches gl.bufferData, it’s native-order memory and endianness is already resolved.

DataView is the boundary tool. Every multi-byte read takes an explicit flag, so parsing a file means stating the format’s byte order instead of inheriting the machine’s:

const dv = new DataView(arrayBuffer);
const x = dv.getFloat32(offset, true);   // true  = little-endian
const n = dv.getUint32(offset, false);   // false = big-endian (the default)

The rule of thumb falls out directly:

  1. Binary arrives from a file or network → parse it once with DataView, using the byte order the format specifies.
  2. Store the results in a TypedArray for the GPU (gl.bufferData, gl.texImage2D).
  3. Never overlay an Int16Array/Float32Array view directly onto downloaded bytes without knowing the source order — on the wrong machine it reads garbage silently.

Where It Bites

Connections

Endianness is the boundary condition of buffers: typed arrays keep bytes in native order precisely so the WebGL upload path (gl.bufferData) can be a straight memcpy, and DataView exists so that boundary — file or network in, native memory out — is crossed exactly once, explicitly.