Skip to main content
Two different rules apply depending on where a value comes from.

Casts round to nearest, ties to even

When a host number enters the format (constructing a value, quantizing an array, a literal in an expression), the exact value is multiplied by the scale and rounded to the nearest integer code. A tie goes to the even code.
The exact attribute shows the fraction you actually got, so you can see the rounding error:

Arithmetic truncates toward zero

Once values are in the format, the hardware computes on integers and drops any fractional part, always toward zero. There is no rounding step inside an operation.
Exactly, 9830 × 9830 / 32768 = 2949.12, and truncation gives 2948, not 2949. Float arithmetic would give 0.09; the chip gives 0.08997, and so does sf. Division truncates too: sf.sf16(0.1) / sf.sf16(0.3) is sf16(0.333343505859375), code 10923, from 3277 × 32768 / 9830 = 10923.3.

Why the difference

Rounding on the way in gives the best possible representation of your data. Truncation inside the arithmetic is what the silicon does, because it is cheaper, and the library’s job is to match the silicon. If you compute something on the host in floats and compare with sf, expect the last code to differ in the direction of zero.

The operations, in one table

a * b + c and sf.fma(a, b, c) give the same bits: the product is truncated once in both. This is tested on every width.

Comparisons never round

<, == and friends compare the exact values. Comparing against a host number does not quantize it first:
See Exactness.