Implement full SSD1306 OLED display support: - Splash screen on boot (1.5s) with project name, version, board - Dashboard showing radio state, config, counters, RSSI/SNR - RSSI history sparkline (64-sample ring buffer, 2px-wide bars) - DisplayOn/DisplayOff host commands routed via dedicated channel - Pure render functions against generic DrawTarget (no hardware coupling) - Display on/off control bypasses radio task entirely Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
305 lines
9.2 KiB
Rust
305 lines
9.2 KiB
Rust
// Copyright 2014-2020 Optimal Computing (NZ) Ltd.
|
|
// Licensed under the MIT license. See LICENSE for details.
|
|
|
|
use core::{f32, f64};
|
|
#[cfg(feature = "num-traits")]
|
|
#[allow(unused_imports)]
|
|
use num_traits::float::FloatCore;
|
|
use super::Ulps;
|
|
|
|
/// A trait for approximate equality comparisons.
|
|
pub trait ApproxEq: Sized {
|
|
/// This type type defines a margin within which two values are to be
|
|
/// considered approximately equal. It must implement `Default` so that
|
|
/// `approx_eq()` can be called on unknown types.
|
|
type Margin: Copy + Default;
|
|
|
|
/// This method tests that the `self` and `other` values are equal within `margin`
|
|
/// of each other.
|
|
fn approx_eq<M: Into<Self::Margin>>(self, other: Self, margin: M) -> bool;
|
|
|
|
/// This method tests that the `self` and `other` values are not within `margin`
|
|
/// of each other.
|
|
fn approx_ne<M: Into<Self::Margin>>(self, other: Self, margin: M) -> bool {
|
|
!self.approx_eq(other, margin)
|
|
}
|
|
}
|
|
|
|
/// This type defines a margin within two `f32` values might be considered equal,
|
|
/// and is intended as the associated type for the `ApproxEq` trait.
|
|
///
|
|
/// Two tests are used to determine approximate equality.
|
|
///
|
|
/// The first test considers two values approximately equal if they differ by <=
|
|
/// `epsilon`. This will only succeed for very small numbers. Note that it may
|
|
/// succeed even if the parameters are of differing signs, straddling zero.
|
|
///
|
|
/// The second test considers how many ULPs (units of least precision, units in
|
|
/// the last place, which is the integer number of floating-point representations
|
|
/// that the parameters are separated by) different the parameters are and considers
|
|
/// them approximately equal if this is <= `ulps`. For large floating-point numbers,
|
|
/// an ULP can be a rather large gap, but this kind of comparison is necessary
|
|
/// because floating-point operations must round to the nearest representable value
|
|
/// and so larger floating-point values accumulate larger errors.
|
|
#[repr(C)]
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct F32Margin {
|
|
pub epsilon: f32,
|
|
pub ulps: i32
|
|
}
|
|
impl Default for F32Margin {
|
|
#[inline]
|
|
fn default() -> F32Margin {
|
|
F32Margin {
|
|
epsilon: f32::EPSILON,
|
|
ulps: 4
|
|
}
|
|
}
|
|
}
|
|
impl F32Margin {
|
|
#[inline]
|
|
pub fn zero() -> F32Margin {
|
|
F32Margin {
|
|
epsilon: 0.0,
|
|
ulps: 0
|
|
}
|
|
}
|
|
pub fn epsilon(self, epsilon: f32) -> Self {
|
|
F32Margin {
|
|
epsilon,
|
|
..self
|
|
}
|
|
}
|
|
pub fn ulps(self, ulps: i32) -> Self {
|
|
F32Margin {
|
|
ulps,
|
|
..self
|
|
}
|
|
}
|
|
}
|
|
impl From<(f32, i32)> for F32Margin {
|
|
fn from(m: (f32, i32)) -> F32Margin {
|
|
F32Margin {
|
|
epsilon: m.0,
|
|
ulps: m.1
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ApproxEq for f32 {
|
|
type Margin = F32Margin;
|
|
|
|
fn approx_eq<M: Into<Self::Margin>>(self, other: f32, margin: M) -> bool {
|
|
let margin = margin.into();
|
|
|
|
// Check for exact equality first. This is often true, and so we get the
|
|
// performance benefit of only doing one compare in most cases.
|
|
self==other ||
|
|
|
|
// Perform epsilon comparison next
|
|
((self - other).abs() <= margin.epsilon) ||
|
|
|
|
{
|
|
// Perform ulps comparion last
|
|
let diff: i32 = self.ulps(&other);
|
|
saturating_abs_i32!(diff) <= margin.ulps
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn f32_approx_eq_test1() {
|
|
let f: f32 = 0.0_f32;
|
|
let g: f32 = -0.0000000000000005551115123125783_f32;
|
|
assert!(f != g); // Should not be directly equal
|
|
assert!(f.approx_eq(g, (f32::EPSILON, 0)) == true);
|
|
}
|
|
#[test]
|
|
fn f32_approx_eq_test2() {
|
|
let f: f32 = 0.0_f32;
|
|
let g: f32 = -0.0_f32;
|
|
assert!(f.approx_eq(g, (f32::EPSILON, 0)) == true);
|
|
}
|
|
#[test]
|
|
fn f32_approx_eq_test3() {
|
|
let f: f32 = 0.0_f32;
|
|
let g: f32 = 0.00000000000000001_f32;
|
|
assert!(f.approx_eq(g, (f32::EPSILON, 0)) == true);
|
|
}
|
|
#[test]
|
|
fn f32_approx_eq_test4() {
|
|
let f: f32 = 0.00001_f32;
|
|
let g: f32 = 0.00000000000000001_f32;
|
|
assert!(f.approx_eq(g, (f32::EPSILON, 0)) == false);
|
|
}
|
|
#[test]
|
|
fn f32_approx_eq_test5() {
|
|
let f: f32 = 0.1_f32;
|
|
let mut sum: f32 = 0.0_f32;
|
|
for _ in 0_isize..10_isize { sum += f; }
|
|
let product: f32 = f * 10.0_f32;
|
|
assert!(sum != product); // Should not be directly equal:
|
|
assert!(sum.approx_eq(product, (f32::EPSILON, 1)) == true);
|
|
assert!(sum.approx_eq(product, F32Margin::zero()) == false);
|
|
}
|
|
#[test]
|
|
fn f32_approx_eq_test6() {
|
|
let x: f32 = 1000000_f32;
|
|
let y: f32 = 1000000.1_f32;
|
|
assert!(x != y); // Should not be directly equal
|
|
assert!(x.approx_eq(y, (0.0, 2)) == true); // 2 ulps does it
|
|
// epsilon method no good here:
|
|
assert!(x.approx_eq(y, (1000.0 * f32::EPSILON, 0)) == false);
|
|
}
|
|
|
|
/// This type defines a margin within two `f64` values might be considered equal,
|
|
/// and is intended as the associated type for the `ApproxEq` trait.
|
|
///
|
|
/// Two tests are used to determine approximate equality.
|
|
///
|
|
/// The first test considers two values approximately equal if they differ by <=
|
|
/// `epsilon`. This will only succeed for very small numbers. Note that it may
|
|
/// succeed even if the parameters are of differing signs, straddling zero.
|
|
///
|
|
/// The second test considers how many ULPs (units of least precision, units in
|
|
/// the last place, which is the integer number of floating-point representations
|
|
/// that the parameters are separated by) different the parameters are and considers
|
|
/// them approximately equal if this is <= `ulps`. For large floating-point numbers,
|
|
/// an ULP can be a rather large gap, but this kind of comparison is necessary
|
|
/// because floating-point operations must round to the nearest representable value
|
|
/// and so larger floating-point values accumulate larger errors.
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct F64Margin {
|
|
pub epsilon: f64,
|
|
pub ulps: i64
|
|
}
|
|
impl Default for F64Margin {
|
|
#[inline]
|
|
fn default() -> F64Margin {
|
|
F64Margin {
|
|
epsilon: f64::EPSILON,
|
|
ulps: 4
|
|
}
|
|
}
|
|
}
|
|
impl F64Margin {
|
|
#[inline]
|
|
pub fn zero() -> F64Margin {
|
|
F64Margin {
|
|
epsilon: 0.0,
|
|
ulps: 0
|
|
}
|
|
}
|
|
pub fn epsilon(self, epsilon: f64) -> Self {
|
|
F64Margin {
|
|
epsilon,
|
|
..self
|
|
}
|
|
}
|
|
pub fn ulps(self, ulps: i64) -> Self {
|
|
F64Margin {
|
|
ulps,
|
|
..self
|
|
}
|
|
}
|
|
}
|
|
impl From<(f64, i64)> for F64Margin {
|
|
fn from(m: (f64, i64)) -> F64Margin {
|
|
F64Margin {
|
|
epsilon: m.0,
|
|
ulps: m.1
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ApproxEq for f64 {
|
|
type Margin = F64Margin;
|
|
|
|
fn approx_eq<M: Into<Self::Margin>>(self, other: f64, margin: M) -> bool {
|
|
let margin = margin.into();
|
|
|
|
// Check for exact equality first. This is often true, and so we get the
|
|
// performance benefit of only doing one compare in most cases.
|
|
self == other ||
|
|
|
|
// Perform epsilon comparison next
|
|
((self - other).abs() <= margin.epsilon) ||
|
|
|
|
{
|
|
// Perform ulps comparion last
|
|
let diff: i64 = self.ulps(&other);
|
|
saturating_abs_i64!(diff) <= margin.ulps
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn f64_approx_eq_test1() {
|
|
let f: f64 = 0.0_f64;
|
|
let g: f64 = -0.0000000000000005551115123125783_f64;
|
|
assert!(f != g); // Should not be precisely equal.
|
|
assert!(f.approx_eq(g, (3.0 * f64::EPSILON, 0)) == true); // 3e is enough.
|
|
// ULPs test won't ever call these equal.
|
|
}
|
|
#[test]
|
|
fn f64_approx_eq_test2() {
|
|
let f: f64 = 0.0_f64;
|
|
let g: f64 = -0.0_f64;
|
|
assert!(f.approx_eq(g, (f64::EPSILON, 0)) == true);
|
|
}
|
|
#[test]
|
|
fn f64_approx_eq_test3() {
|
|
let f: f64 = 0.0_f64;
|
|
let g: f64 = 1e-17_f64;
|
|
assert!(f.approx_eq(g, (f64::EPSILON, 0)) == true);
|
|
}
|
|
#[test]
|
|
fn f64_approx_eq_test4() {
|
|
let f: f64 = 0.00001_f64;
|
|
let g: f64 = 0.00000000000000001_f64;
|
|
assert!(f.approx_eq(g, (f64::EPSILON, 0)) == false);
|
|
}
|
|
#[test]
|
|
fn f64_approx_eq_test5() {
|
|
let f: f64 = 0.1_f64;
|
|
let mut sum: f64 = 0.0_f64;
|
|
for _ in 0_isize..10_isize { sum += f; }
|
|
let product: f64 = f * 10.0_f64;
|
|
assert!(sum != product); // Should not be precisely equaly.
|
|
assert!(sum.approx_eq(product, (f64::EPSILON, 0)) == true);
|
|
assert!(sum.approx_eq(product, (0.0, 1)) == true);
|
|
}
|
|
#[test]
|
|
fn f64_approx_eq_test6() {
|
|
let x: f64 = 1000000_f64;
|
|
let y: f64 = 1000000.0000000003_f64;
|
|
assert!(x != y); // Should not be precisely equal.
|
|
assert!(x.approx_eq(y, (0.0, 3)) == true);
|
|
}
|
|
#[test]
|
|
fn f64_code_triggering_issue_20() {
|
|
assert_eq!((-25.0f64).approx_eq(25.0, (0.00390625, 1)), false);
|
|
}
|
|
|
|
impl<T> ApproxEq for &[T]
|
|
where T: Copy + ApproxEq {
|
|
type Margin = <T as ApproxEq>::Margin;
|
|
|
|
fn approx_eq<M: Into<Self::Margin>>(self, other: Self, margin: M) -> bool {
|
|
let margin = margin.into();
|
|
if self.len() != other.len() { return false; }
|
|
self.iter().zip(other.iter()).all(|(a,b)| {
|
|
a.approx_eq(*b, margin)
|
|
})
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_slices() {
|
|
assert!( [1.33, 2.4, 2.5].approx_eq(&[1.33, 2.4, 2.5], (0.0, 0_i64)) );
|
|
assert!( ! [1.33, 2.4, 2.6].approx_eq(&[1.33, 2.4, 2.5], (0.0, 0_i64)) );
|
|
assert!( ! [1.33, 2.4].approx_eq(&[1.33, 2.4, 2.5], (0.0, 0_i64)) );
|
|
assert!( ! [1.33, 2.4, 2.5].approx_eq(&[1.33, 2.4], (0.0, 0_i64)) );
|
|
}
|
|
|