Task 3 of 5

Where Does 7.35 Go?

Real measurements are not tidy little category codes. samples holds 4,096 sensor readings spread over −32 … 32, and sixteen bins across that span makes each bin 4 units wide. Turning a reading into a bin index is one division and one floor:

// lo = -32, span = 64, bins = 16
const bin = Math.floor((v - lo) / span * bins);

Two details decide whether the histogram is right, and both of them are where real bugs live. First, a bin is half-open: bin 14 is [24, 28), so 24 belongs to it and 28 belongs to bin 15. Math.floor gets that for free — which is exactly why the index is floored and not rounded.

Second, the top edge. A reading exactly equal to the maximum maps to (32 − −32) / 64 × 16 = 16 — bin 16, one past the last thread, owned by nobody. Four samples here sit exactly on it, and without a clamp all four silently stop existing: the counts come to 4,092 instead of 4,096. Clamp the index with Math.min(bins − 1, …) and they land in the last real bin, which is what closes that bin at the top.

Every sample here is inside the range, so the clamp only ever has to catch the maximum. When data really can fall outside the range, clamping quietly piles the outliers into the end bins and the total will not say a word about it — so that becomes a decision to make on purpose: clamp them in, or drop them out. (And when the range comes from the data rather than from you, a min and a max reduction is where it comes from.)

bins are half-open, and the last one only closes because you clamped it
Goal: histogram the 4,096 samples into 16 bins with a clamped index, so the counts total 4,096 — and log that total.

Requirements

Hint 1 — run it first

The starter already computes an unclamped index and already totals the counts. Run it: the total comes out 4,092. Four readings went into a bin that does not exist. That gap is the whole task.

Hint 2 — the clamp
const bin = Math.min(this.constants.bins - 1, Math.floor(raw));

— and nothing else changes.

Hint 3 — why floor and not round

Math.round looks harmless and moves every reading that is more than half way through its bin into the next one — a histogram shifted by half a bin, with the right total. The total will not catch that one; only knowing the rule will.

Same idea elsewhere

Quantizing a continuous value into an integer index is everywhere in GPU work: picking a mip level, hashing a particle into a spatial grid cell, indexing a lookup table, choosing a colour ramp entry. Every platform ships the clamp as a primitive — clamp() in GLSL, WGSL and MSL, __saturatef and clamped texture address modes in CUDA — because the same off-by-one at the top edge has bitten everybody. NVIDIA's own histogram samples clamp for exactly this reason.

All tasks in Histograms & Binning

  1. The Increment That Vanishes
  2. One Thread Per Bin
  3. Where Does 7.35 Go?
  4. Partial Histograms, Then Merge
  5. Payoff: An Image's Tone Histogram

This page is an interactive exercise — the editor, the GPU runner and your saved progress need JavaScript. The text above is the full brief.