Task 6 of 6
The sort has a requirement it never had to say out loud: the key must be a
non-negative integer, because Math.floor(key / place) % 16
is only a digit for those. Hand it −5 and the "digit" is −5,
starts[−5] is off the front of the bucket table, and the pass returns junk.
Signed integers have a clean fix that costs one map each way: bias them. Add 2,048 and the range −2048…2047 becomes 0…4095 — same order, all non-negative. Sort, then subtract the 2,048 back off. Production libraries call this step encoding the key, and the rule is the only one that matters: any order-preserving, invertible map into the unsigned integers makes radix sort work on your type.
Floats are the same idea and a harder map — and this is where gpu.js stops. A float's
ordering is its bit pattern's ordering, for positives; IEEE-754 negatives carry a
sign bit on top and sort backwards under an unsigned comparison, so real implementations
reinterpret the 32 bits and flip them (x ^ 0x80000000 for a positive,
~x for a negative) before sorting and flip back after. gpu.js does have
&, |, ^, << and
>> inside kernels, but the WebGL backend emulates them with GLSL integer
loops and they part company with JavaScript the moment an operand goes negative
(-8 & 15 is 8 in JavaScript and on the CPU backend, and 0 on WebGL). More
to the point, there is no way to see a float's bits at all: GLSL ES 1.00 has no
floatBitsToInt and gpu.js exposes none, so key & 15 truncates
the value to an integer first — 3.5 & 15 is 3, the number's integer part,
never its bit pattern. This course therefore sorts non-negative integer keys, and signed
ones through the bias below; a CUDA or WebGPU implementation runs the same six kernels with
a bit-flipping encoder in front.
readings, which run from −2048 to 2047, by
biasing them into non-negative integers, sorting, and taking the bias back off.encode adds this.constants.bias to every readingdecode subtracts it againradixSort on the encoded values, and decode the resultconsole.log the sorted readings' smallest and largest valuesBoth kernels are one-line maps over their own cell — one adds
this.constants.bias, the other subtracts it. Nothing about the sort
changes.
const sorted = await decode(await radixSort(await encode(readings)));
Encode on the way in, decode on the way out. Miss the decode and every value comes back 2,048 too high; miss the encode and the negative keys index off the front of the bucket table.
This page is an interactive exercise — the editor, the GPU runner and your saved progress need JavaScript. The text above is the full brief.