[slider] only show full precision if significant
@@ -86,7 +86,8 @@ pub fn round_to_precision(value: f32, decimal_places: usize) -> f32 {
pub fn format_with_minimum_precision(value: f32, precision: usize) -> String {
let text = format!("{:.*}", precision, value);
- if (text.parse::<f32>().unwrap() - value).abs() <= std::f32::EPSILON {
+ let epsilon = 16.0 * f32::EPSILON; // margin large enough to handle most peoples round-tripping needs
+ if almost_equal(text.parse::<f32>().unwrap(), value, epsilon) {
// Enough precision to show the value accurately - good!
text
} else {
@@ -96,3 +97,52 @@ pub fn format_with_minimum_precision(value: f32, precision: usize) -> String {
value.to_string()
}
}
+
+/// Should return true when arguments are the same within some rounding error.
+/// For instance `almost_equal(x, x.to_degrees().to_radians(), f32::EPSILON)` should hold true for all x.
+/// The `epsilon` can be `f32::EPSILON` to handle simple transforms (like degrees -> radians)
+/// but should be higher to handle more complex transformations.
+pub fn almost_equal(a: f32, b: f32, epsilon: f32) -> bool {
+ #![allow(clippy::float_cmp)]
+
+ if a == b {
+ true // handle infinites
+ } else {
+ let abs_max = a.abs().max(b.abs());
+ abs_max <= epsilon || ((a - b).abs() / abs_max) <= epsilon
+ }
+}
+
+#[test]
+fn test_almost_equal() {
+ for &x in &[
+ 0.0_f32,
+ f32::MIN_POSITIVE,
+ 1e-20,
+ 1e-10,
+ f32::EPSILON,
+ 0.1,
+ 0.99,
+ 1.0,
+ 1.001,
+ 1e10,
+ f32::MAX / 100.0,
+ // f32::MAX, // overflows in rad<->deg test
+ f32::INFINITY,
+ ] {
+ for &x in &[-x, x] {
+ for roundtrip in &[
+ |x: f32| x.to_degrees().to_radians(),
+ |x: f32| x.to_radians().to_degrees(),
+ ] {
+ let epsilon = f32::EPSILON;
+ assert!(
+ almost_equal(x, roundtrip(x), epsilon),
+ "{} vs {}",
+ x,
+ roundtrip(x)
+ );
+ }
+ }
+ }
+}
