# Where Does 7.35 Go?

*Task 3 of 5 · [Histograms & Binning](https://gpu.rocks/learn/histograms-and-binning-dfb254f4.md) · GPU.js Learn*

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:

```js
// 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.)

## Figures

- **bins are half-open, and the last one only closes because you clamped it**

## Goal

**Goal:** histogram the 4,096 `samples` into 16 bins with a
clamped index, so the counts total 4,096 — and log that total.

## Requirements

- Map each sample with `(v − this.constants.lo) / this.constants.span * this.constants.bins`, floored
- Clamp the index to `this.constants.bins - 1` so the maximum lands in the last bin instead of falling out
- Count a sample only when its bin equals `this.thread.x`
- `console.log` the total of the 16 counts — it must be `4096`

## 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

```js
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.

## Starter code

```js
// 16 bins over -32 ... 32, so every bin is 4 units wide.
const gpu = new GPU({ mode });

const histogram = gpu.createKernel(function (samples) {
  let count = 0;
  for (let i = 0; i < this.constants.n; i++) {
    const raw = (samples[i] - this.constants.lo)
      / this.constants.span * this.constants.bins;
    // TODO: floor alone sends a sample equal to the maximum to bin 16,
    // which no thread owns. Clamp the index to this.constants.bins - 1.
    const bin = Math.floor(raw);
    if (bin === this.thread.x) count++;
  }
  return count;
}, {
  output: [16],
  constants: { n: 4096, bins: 16, lo: -32, span: 64 },
});

const counts = await histogram(samples);
console.log('counts:', counts);

let total = 0;
for (let b = 0; b < counts.length; b++) total += counts[b];
console.log('total:', total);   // must be 4096, and right now it is not
```

---

Interactive version: https://gpu.rocks/learn/histograms-and-binning-dfb254f4/3

[Previous task](https://gpu.rocks/learn/histograms-and-binning-dfb254f4/2.md) · [Next task](https://gpu.rocks/learn/histograms-and-binning-dfb254f4/4.md)
