mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-24 09:57:08 -06:00
fix(algo): keep the oil-paint rounding exact just below a half
`round_i32` added the half in `f32`, where a value within one ulp of a half-integer can round across it: `f32::from_bits(0x3eff_ffff)`, the largest `f32` below `0.5`, plus `0.5` rounds up to `1.0`, so the helper returned `1` where `f32::round` returns `0` and the LIC could select a different tangent or bristle-noise coordinate than the build this PR measures against. Adding the half in `f64` is exact for every window coordinate, so the helper matches `f32::round` on that boundary again and the pixels are main's. `rounding_matches_the_standard_library` now covers both half-integer boundaries and the two `f32` steps either side of every integer a tile coordinate can reach; it fails on the `f32` form at `0.49999997`.
This commit is contained in:
+17
-3
@@ -31,11 +31,13 @@ fn stroke_len(stylization: f32) -> f32 {
|
||||
}
|
||||
|
||||
/// `f32::round` (half away from zero) as an integer, without the `roundf` call `f32::round`
|
||||
/// makes on the baseline x86-64 target. Exact for `|v| < 2^22`, which covers every window
|
||||
/// coordinate; larger magnitudes saturate like the cast the caller used to perform.
|
||||
/// makes on the baseline x86-64 target. The half is added in `f64`, where the sum is exact for
|
||||
/// every `|v| < 2^22` (all window coordinates): in `f32` the largest value below `0.5` plus `0.5`
|
||||
/// rounds up to `1.0`, which would return `1` where `f32::round` returns `0`. Larger magnitudes
|
||||
/// saturate like the cast the caller used to perform.
|
||||
#[inline]
|
||||
fn round_i32(v: f32) -> i32 {
|
||||
(v + 0.5f32.copysign(v)) as i32
|
||||
(f64::from(v) + 0.5f64.copysign(f64::from(v))) as i32
|
||||
}
|
||||
|
||||
/// [`round_i32`] clamped into `0..n`.
|
||||
@@ -231,6 +233,18 @@ mod tests {
|
||||
}
|
||||
v += 0.25;
|
||||
}
|
||||
// The half-integer boundaries are where a `f32` add of the half rounds across the value
|
||||
// `f32::round` keeps below: `0x3eff_ffff` is the largest `f32` under `0.5`.
|
||||
for v in [0.5f32, -0.5, f32::from_bits(0x3eff_ffff), f32::from_bits(0xbeff_ffff)] {
|
||||
assert_eq!(round_i32(v), v.round() as i32, "round_i32({v})");
|
||||
}
|
||||
// Every value one and two steps either side of an integer the LIC coordinates can reach.
|
||||
for k in -40i32..=2100 {
|
||||
let f = k as f32;
|
||||
for v in [f.next_down().next_down(), f.next_down(), f, f.next_up()] {
|
||||
assert_eq!(round_i32(v), v.round() as i32, "round_i32({v}) near {k}");
|
||||
}
|
||||
}
|
||||
assert_eq!(round_i32(f32::NAN), f32::NAN.round() as i32);
|
||||
for n in [1usize, 8] {
|
||||
assert_eq!(round_index(f32::NEG_INFINITY, n), (f32::NEG_INFINITY.clamp(0.0, (n - 1) as f32)) as usize);
|
||||
|
||||
Reference in New Issue
Block a user