f8 0.2.0

A no_std, one-byte UNORM with exact rounding, saturating arithmetic, and SIMD conversion
Documentation
# Exact UNORM8 Arithmetic

## Representation

The type stores `b: u8` and denotes `b/255`. It has 256 equally spaced real
values, not a floating exponent or IEEE exceptional encodings. Nearest-even
quantization minimizes absolute error on this grid; ties select an even byte.
NaN is explicitly mapped to zero rather than being stored.

The representation matches the unsigned-normalized interpretation described by
[Vulkan's fixed-point conversion rules](https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#fundamentals-fixedfpconv).
The crate additionally specifies exact conversion and deterministic tie-breaking;
it does not claim bit-identical conversion with every graphics implementation.

## Encoding Without Floating-Point Rounding

For finite nonnegative binary32 values, the unsigned bit pattern has numerical
ordering. Negative inputs and NaNs can therefore be rejected by integer range
checks. Values below `2^-9` are below the first threshold `1/510` and encode to
zero; values at or above one saturate to 255.

For the remaining exponent fields `118..=126`:

```text
m = fraction_bits | 0x800000           (24-bit significand)
x = m * 2^(exponent - 150)
255*x = (m*255) / 2^(150 - exponent)
```

`m*255` fits in `u32`. Set `shift = 149 - exponent`, in `23..=31`, and retain
one guard bit when shifting:

```text
guard = product >> shift
lower = guard >> 1
sticky = product & ((1 << shift) - 1)
increment = (guard & 1) && (sticky != 0 || lower is odd)
result = lower + increment
```

The guard bit distinguishes the upper half, the sticky bits distinguish a true
tie from a value above the midpoint, and the low bit of `lower` selects the even
result. This needs neither a shift by 32 nor a potentially overflowing addition
of a rounding bias. All intermediate bounds are valid in debug and const evaluation.

The AVX2 kernel applies the same algorithm to eight lanes. Integer classification
clears negative values and NaNs, and clamps other bit patterns to `[2^-9, 1]`.
The lower endpoint still rounds to zero and the upper endpoint rounds to 255.
Packing is performed only after every lane is in `0..=255`.

An independent reference uses `(f64::from(x) * 255.0).round_ties_even() as u8`:
the exact product requires at most 32 significant bits, fitting comfortably in
binary64's 53. A binary32 multiplication is not an adequate oracle.

## Decoding Without Division

For `0 < b < 255`, the binary expansion of `b/255` repeats the eight bits of `b`:

```text
0.bbbbbbbb bbbbbbbb bbbbbbbb ...
```

Multiplication by `0x01010101` constructs four repetitions. Count leading zeros,
normalize, retain 24 significant bits, and round using bit 25. There can be no
exact midpoint: a nonzero repeating fraction cannot terminate, so a set guard
bit always has nonzero bits following it. Encoding 255 carries the rounded
significand into the exponent to give exactly `1.0`; zero is handled separately.

The resulting `f32::from_bits` value is bit-identical to correctly rounded
division by 255 for all 256 inputs. Hardware bulk decoding may instead use
division, allowing LLVM to widen an ordinary Rust loop. Multiplication by an
approximate reciprocal is not substituted: it can differ in the final bit.

## Arithmetic

For input bytes `a` and `b`, the output byte is:

| Operation | Formula |
| --- | --- |
| Addition | `min(a+b, 255)` |
| Subtraction | `max(a-b, 0)` |
| Multiplication | nearest-even `a*b/255` |
| Division | `min(nearest-even(255*a/b), 255)` for nonzero `b` |

Multiplication cannot tie because 255 is odd, so `(a*b+127)/255` with `u16`
intermediates is exact. Division compares twice the remainder to the denominator,
then uses quotient parity on equality. Early saturation and zero handling avoid
zero divisors and overflowing outputs. `0/0` returns zero; positive `/0` returns one.

Only addition is an associative saturating reduction here. Rounded products
depend on grouping and order. `Sum` and `Product` consume the whole iterator and
quantize after each step, rather than silently changing precision or skipping
iterator side effects after an absorbing value.

## Safety Boundaries

Scalar conversion and all arithmetic are safe Rust. Slice casts rely only on
the transparent `u8` layout, all-bit-pattern validity, and preserved Rust borrows.
The assembly kernel is private, requires equal-length disjoint slices and AVX2,
reads complete eight-float chunks, writes exactly eight bytes per chunk, and
handles tails in Rust. Unaligned accesses are intentional. Register clobbers and
memory effects are declared; the assembly uses no stack, does not modify integer
flags, and does not read or modify floating-point control state.

Runtime dispatch verifies CPU support and enabled XMM/YMM OS state before entering
the kernel. It is not used in enclaves, kernels, or UEFI, where those checks
cannot establish permission to execute the instructions in the current context.