Floats as written, never fused; fma and muladd; f32/f64 bits, f16 and bf16

- a * b + c is two roundings on every processor and --cpu: the IR has no fast-math flags, and clang
  compiles it with -ffp-contract=on (checked in the assembly: never fused, also with FMA hardware)
- fma(a, b, c) / v.fma(b, c): one rounding, always (llvm.fma)
- muladd(a, b, c) / v.muladd(b, c): the fastest way here (llvm.fmuladd), what v.fma was before
- f32_bits / f32_from_bits / f64_bits / f64_from_bits: bitcasts, also of vectors
- f16_to_f32 / f32_to_f16 (LLVM half), bf16_to_f32 / f32_to_bf16 (integer ops, ties to even, NaN kept)
- docs (en, pl): what is guaranteed about floats, the new built-ins

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Psc42moPLwP43Cmb9oZqSR
This commit is contained in:
Claude 2026-11-01 15:25:40 +00:00
commit 0ceaaa27c4
No known key found for this signature in database
13 changed files with 373 additions and 22 deletions

31
tests/run/floatbits.fch Normal file
View file

@ -0,0 +1,31 @@
// a * b + c is never fused; fma rounds once; numbers as their bits; f16 and bf16
fn plain(float a, float b, float c) -> float {
return a * b + c
}
fn main() {
// 0.1 * 10 is 1.0000000000000000555..., which rounds to 1 before the - 1 (two roundings), but not inside fma
print(plain(0.1, 10.0, -1.0), fma(0.1, 10.0, -1.0))
print(fma(f32(0.1), 10.0, -1.0), fma(2, 3, 4))
m := muladd(1.5, 2.0, 0.25) // fused or not, exact here
print(m, f32x4(1.5).muladd(2.0, 0.25), fma(f32x4(0.1), 10.0, f32x4(-1.0))[0])
det := f32x4(0.1) * f32x4(10.0) - f32x4(1.0)
print(det[0])
// bits
print(f32_bits(1.0), f64_bits(1.0), f32_bits(-0.0))
print(f32_from_bits(0x40490fdb), f64_from_bits(f64_bits(2.5)), f32_from_bits(i32(-1082130432)))
u32 inf = 0x7f800000
print(f32_from_bits(inf), f32_bits(f32x4(1.0, 2.0, -2.0, 0.5)))
// f16
print(f16_to_f32(0x3c00), f16_to_f32(0xc000), f16_to_f32(0x7bff), f16_to_f32(0x0001))
print(f32_to_f16(0.1), f32_to_f16(65504.0), f32_to_f16(1.0e6), f16_to_f32(f32_to_f16(0.333)))
print(f16_to_f32(u16x8(0x3c00, 0x4000, 0x4200, 0x4400, 0, 0x8000, 0x3800, 0x3400)))
// bf16
print(bf16_to_f32(0x3f80), bf16_to_f32(0x4049), f32_to_bf16(3.14159))
print(f32_to_bf16(f32_from_bits(0x3f808000)), f32_to_bf16(f32_from_bits(0x3f818000)), f32_to_bf16(f32_from_bits(0x7fc00001)))
print(f32_to_bf16(f32x4(1.0, -2.0, 0.5, 3.14159)), bf16_to_f32(u16x4(0x3f80, 0xc000, 0x3f00, 0x4049)))
}

13
tests/run/floatbits.out Normal file
View file

@ -0,0 +1,13 @@
0 5.551115123125783e-17
1.4901161e-08 10
3.25 f32x4(3.25, 3.25, 3.25, 3.25) 1.4901161e-08
0
1065353216 4607182418800017408 2147483648
3.1415927 2.5 -1
inf u32x4(1065353216, 1073741824, 3221225472, 1056964608)
1 -2 65504 5.9604645e-08
11878 31743 31744 0.3330078
f32x8(1, 2, 3, 4, 0, -0, 0.5, 0.25)
1 3.140625 16457
16256 16258 32704
u16x4(16256, 49152, 16128, 16457) f32x4(1, -2, 0.5, 3.140625)

View file

@ -14,7 +14,7 @@ fn dot([]f32 a, []f32 b) -> f32 {
acc := f32x8(0.0)
i := 0
while i + 8 <= a.len {
acc = f32x8(a, i).fma(f32x8(b, i), acc)
acc = f32x8(a, i).muladd(f32x8(b, i), acc)
i += 8
}
total := acc.sum()