feat: add display UI with splash screen, dashboard, and RSSI sparkline
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>
This commit is contained in:
17624 files changed
+4564928
-38
No files matched your search
Vendored
+69
@@ -0,0 +1,69 @@
|
||||
// Copyright © 2019–2021 Trevor Spiteri
|
||||
|
||||
// This library is free software: you can redistribute it and/or
|
||||
// modify it under the terms of either
|
||||
//
|
||||
// * the Apache License, Version 2.0 or
|
||||
// * the MIT License
|
||||
//
|
||||
// at your option.
|
||||
//
|
||||
// You should have recieved copies of the Apache License and the MIT
|
||||
// License along with the library. If not, see
|
||||
// <https://www.apache.org/licenses/LICENSE-2.0> and
|
||||
// <https://opensource.org/licenses/MIT>.
|
||||
|
||||
use crate::{cast, Cast, CheckedCast, Round, UnwrappedCast};
|
||||
use core::fmt::{Debug, Display, Formatter, LowerExp, Result as FmtResult, UpperExp};
|
||||
|
||||
macro_rules! convert {
|
||||
($($Src:ty),* => $Dst:ty) => { $(
|
||||
impl Cast<$Dst> for $Src {
|
||||
#[inline]
|
||||
fn cast(self) -> $Dst {
|
||||
self as $Dst
|
||||
}
|
||||
}
|
||||
|
||||
impl CheckedCast<$Dst> for $Src {
|
||||
#[inline]
|
||||
fn checked_cast(self) -> Option<$Dst> {
|
||||
Some(cast(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl UnwrappedCast<$Dst> for $Src {
|
||||
#[inline]
|
||||
fn unwrapped_cast(self) -> $Dst {
|
||||
cast(self)
|
||||
}
|
||||
}
|
||||
)* };
|
||||
}
|
||||
|
||||
convert! { i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, f64 => f32 }
|
||||
convert! { i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, f64 => f64 }
|
||||
|
||||
impl<T: Display> Display for Round<T> {
|
||||
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
||||
Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug> Debug for Round<T> {
|
||||
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
||||
Debug::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: LowerExp> LowerExp for Round<T> {
|
||||
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
||||
LowerExp::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: UpperExp> UpperExp for Round<T> {
|
||||
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
||||
UpperExp::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
Vendored
+542
@@ -0,0 +1,542 @@
|
||||
// Copyright © 2019–2021 Trevor Spiteri
|
||||
|
||||
// This library is free software: you can redistribute it and/or
|
||||
// modify it under the terms of either
|
||||
//
|
||||
// * the Apache License, Version 2.0 or
|
||||
// * the MIT License
|
||||
//
|
||||
// at your option.
|
||||
//
|
||||
// You should have recieved copies of the Apache License and the MIT
|
||||
// License along with the library. If not, see
|
||||
// <https://www.apache.org/licenses/LICENSE-2.0> and
|
||||
// <https://opensource.org/licenses/MIT>.
|
||||
|
||||
use crate::{
|
||||
cast, checked_cast, overflowing_cast, saturating_cast, wrapping_cast, Cast, CheckedCast,
|
||||
OverflowingCast, Round, SaturatingCast, UnwrappedCast, WrappingCast,
|
||||
};
|
||||
use core::{mem, num::Wrapping};
|
||||
|
||||
macro_rules! bool_to_int {
|
||||
($Dst:ty) => {
|
||||
impl Cast<$Dst> for bool {
|
||||
#[inline]
|
||||
fn cast(self) -> $Dst {
|
||||
self as $Dst
|
||||
}
|
||||
}
|
||||
|
||||
impl CheckedCast<$Dst> for bool {
|
||||
#[inline]
|
||||
fn checked_cast(self) -> Option<$Dst> {
|
||||
Some(self as $Dst)
|
||||
}
|
||||
}
|
||||
|
||||
impl SaturatingCast<$Dst> for bool {
|
||||
#[inline]
|
||||
fn saturating_cast(self) -> $Dst {
|
||||
self as $Dst
|
||||
}
|
||||
}
|
||||
|
||||
impl WrappingCast<$Dst> for bool {
|
||||
#[inline]
|
||||
fn wrapping_cast(self) -> $Dst {
|
||||
self as $Dst
|
||||
}
|
||||
}
|
||||
|
||||
impl OverflowingCast<$Dst> for bool {
|
||||
#[inline]
|
||||
fn overflowing_cast(self) -> ($Dst, bool) {
|
||||
(self as $Dst, false)
|
||||
}
|
||||
}
|
||||
|
||||
impl UnwrappedCast<$Dst> for bool {
|
||||
#[inline]
|
||||
fn unwrapped_cast(self) -> $Dst {
|
||||
self as $Dst
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! common {
|
||||
($Src:ty => $Dst:ty) => {
|
||||
impl Cast<$Dst> for $Src {
|
||||
#[inline]
|
||||
#[cfg_attr(track_caller, track_caller)]
|
||||
fn cast(self) -> $Dst {
|
||||
let (wrapped, overflow) = overflowing_cast(self);
|
||||
debug_assert!(!overflow, "{} overflows", self);
|
||||
let _ = overflow;
|
||||
wrapped
|
||||
}
|
||||
}
|
||||
|
||||
impl CheckedCast<$Dst> for $Src {
|
||||
#[inline]
|
||||
fn checked_cast(self) -> Option<$Dst> {
|
||||
match overflowing_cast(self) {
|
||||
(value, false) => Some(value),
|
||||
(_, true) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SaturatingCast<$Dst> for $Src {
|
||||
#[inline]
|
||||
fn saturating_cast(self) -> $Dst {
|
||||
match overflowing_cast(self) {
|
||||
(value, false) => value,
|
||||
(_, true) => {
|
||||
if self > 0 {
|
||||
<$Dst>::max_value()
|
||||
} else {
|
||||
<$Dst>::min_value()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WrappingCast<$Dst> for $Src {
|
||||
#[inline]
|
||||
fn wrapping_cast(self) -> $Dst {
|
||||
overflowing_cast(self).0
|
||||
}
|
||||
}
|
||||
|
||||
impl UnwrappedCast<$Dst> for $Src {
|
||||
#[inline]
|
||||
fn unwrapped_cast(self) -> $Dst {
|
||||
match overflowing_cast(self) {
|
||||
(value, false) => value,
|
||||
(_, true) => panic!("overflow"),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! same_signedness {
|
||||
($($Src:ty),* => $Dst:ty) => { $(
|
||||
common! { $Src => $Dst }
|
||||
|
||||
impl OverflowingCast<$Dst> for $Src {
|
||||
#[inline]
|
||||
fn overflowing_cast(self) -> ($Dst, bool) {
|
||||
let wrapped = self as $Dst;
|
||||
let overflow = self != wrapped as $Src;
|
||||
(wrapped, overflow)
|
||||
}
|
||||
}
|
||||
)* };
|
||||
}
|
||||
|
||||
macro_rules! signed_to_unsigned {
|
||||
($($Src:ty),* => $Dst:ty) => { $(
|
||||
common! { $Src => $Dst }
|
||||
|
||||
impl OverflowingCast<$Dst> for $Src {
|
||||
#[inline]
|
||||
fn overflowing_cast(self) -> ($Dst, bool) {
|
||||
let wrapped = self as $Dst;
|
||||
let overflow = self < 0 || self != wrapped as $Src;
|
||||
(wrapped, overflow)
|
||||
}
|
||||
}
|
||||
)* };
|
||||
}
|
||||
|
||||
macro_rules! unsigned_to_signed {
|
||||
($($Src:ty),* => $Dst:ty) => { $(
|
||||
common! { $Src => $Dst }
|
||||
|
||||
impl OverflowingCast<$Dst> for $Src {
|
||||
#[inline]
|
||||
fn overflowing_cast(self) -> ($Dst, bool) {
|
||||
let wrapped = self as $Dst;
|
||||
let overflow = wrapped < 0 || self != wrapped as $Src;
|
||||
(wrapped, overflow)
|
||||
}
|
||||
}
|
||||
)* };
|
||||
}
|
||||
|
||||
macro_rules! wrapping_int {
|
||||
($($Src:ty),* => $Dst:ty) => { $(
|
||||
impl Cast<Wrapping<$Dst>> for $Src {
|
||||
#[inline]
|
||||
fn cast(self) -> Wrapping<$Dst> {
|
||||
Wrapping(wrapping_cast(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl CheckedCast<Wrapping<$Dst>> for $Src {
|
||||
#[inline]
|
||||
fn checked_cast(self) -> Option<Wrapping<$Dst>> {
|
||||
Some(cast(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl UnwrappedCast<Wrapping<$Dst>> for $Src {
|
||||
#[inline]
|
||||
fn unwrapped_cast(self) -> Wrapping<$Dst> {
|
||||
cast(self)
|
||||
}
|
||||
}
|
||||
)* };
|
||||
}
|
||||
|
||||
macro_rules! float_to_int {
|
||||
($Src:ty, $ViaU:ty, $ViaI:ty => $($Dst:ty)*) => { $(
|
||||
impl Cast<$Dst> for $Src {
|
||||
#[inline]
|
||||
#[cfg_attr(track_caller, track_caller)]
|
||||
fn cast(self) -> $Dst {
|
||||
let (wrapped, overflow) = overflowing_cast(self);
|
||||
debug_assert!(!overflow, "{} overflows", self);
|
||||
let _ = overflow;
|
||||
wrapped
|
||||
}
|
||||
}
|
||||
|
||||
impl CheckedCast<$Dst> for $Src {
|
||||
fn checked_cast(self) -> Option<$Dst> {
|
||||
let f: Float<$ViaU> = self.into();
|
||||
match f.kind {
|
||||
FloatKind::Nan | FloatKind::Infinite | FloatKind::Overflowing(_, true) => None,
|
||||
FloatKind::Overflowing(abs, false) => {
|
||||
if f.neg {
|
||||
let i = abs as $ViaI;
|
||||
if i == <$ViaI>::min_value() {
|
||||
checked_cast(i)
|
||||
} else if i < 0 {
|
||||
None
|
||||
} else {
|
||||
checked_cast(-i)
|
||||
}
|
||||
} else {
|
||||
checked_cast(abs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SaturatingCast<$Dst> for $Src {
|
||||
#[cfg_attr(track_caller, track_caller)]
|
||||
fn saturating_cast(self) -> $Dst {
|
||||
let f: Float<$ViaU> = self.into();
|
||||
let saturated = if f.neg {
|
||||
<$Dst>::min_value()
|
||||
} else {
|
||||
<$Dst>::max_value()
|
||||
};
|
||||
match f.kind {
|
||||
FloatKind::Nan => panic!("NaN"),
|
||||
FloatKind::Infinite | FloatKind::Overflowing(_, true) => saturated,
|
||||
FloatKind::Overflowing(abs, false) => {
|
||||
if f.neg {
|
||||
let i = abs as $ViaI;
|
||||
if i == <$ViaI>::min_value() {
|
||||
saturating_cast(i)
|
||||
} else if i < 0 {
|
||||
saturated
|
||||
} else {
|
||||
saturating_cast(-i)
|
||||
}
|
||||
} else {
|
||||
saturating_cast(abs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WrappingCast<$Dst> for $Src {
|
||||
#[inline]
|
||||
#[cfg_attr(track_caller, track_caller)]
|
||||
fn wrapping_cast(self) -> $Dst {
|
||||
overflowing_cast(self).0
|
||||
}
|
||||
}
|
||||
|
||||
impl OverflowingCast<$Dst> for $Src {
|
||||
#[cfg_attr(track_caller, track_caller)]
|
||||
fn overflowing_cast(self) -> ($Dst, bool) {
|
||||
let f: Float<$ViaU> = self.into();
|
||||
match f.kind {
|
||||
FloatKind::Nan => panic!("NaN"),
|
||||
FloatKind::Infinite => panic!("infinite"),
|
||||
FloatKind::Overflowing(abs, overflow) => {
|
||||
if f.neg {
|
||||
let i = abs as $ViaI;
|
||||
let (wrapped, overflow2) = if i == <$ViaI>::min_value() {
|
||||
overflowing_cast(i)
|
||||
} else if i < 0 {
|
||||
(wrapping_cast::<_, $Dst>(abs).wrapping_neg(), true)
|
||||
} else {
|
||||
overflowing_cast(-i)
|
||||
};
|
||||
(wrapped, overflow | overflow2)
|
||||
} else {
|
||||
let (wrapped, overflow2) = overflowing_cast(abs);
|
||||
(wrapped, overflow | overflow2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UnwrappedCast<$Dst> for $Src {
|
||||
#[inline]
|
||||
fn unwrapped_cast(self) -> $Dst {
|
||||
match overflowing_cast(self) {
|
||||
(val, false) => val,
|
||||
(_, true) => panic!("overflow"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Cast<Wrapping<$Dst>> for $Src {
|
||||
#[inline]
|
||||
#[cfg_attr(track_caller, track_caller)]
|
||||
fn cast(self) -> Wrapping<$Dst> {
|
||||
Wrapping(wrapping_cast(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl CheckedCast<Wrapping<$Dst>> for $Src {
|
||||
fn checked_cast(self) -> Option<Wrapping<$Dst>> {
|
||||
let f: Float<$ViaU> = self.into();
|
||||
match f.kind {
|
||||
FloatKind::Nan | FloatKind::Infinite => None,
|
||||
FloatKind::Overflowing(abs, _) => {
|
||||
let wrapped = if f.neg {
|
||||
let i = abs as $ViaI;
|
||||
if i == <$ViaI>::min_value() {
|
||||
wrapping_cast(i)
|
||||
} else if i < 0 {
|
||||
wrapping_cast::<_, $Dst>(abs).wrapping_neg()
|
||||
} else {
|
||||
wrapping_cast(-i)
|
||||
}
|
||||
} else {
|
||||
wrapping_cast(abs)
|
||||
};
|
||||
Some(Wrapping(wrapped))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UnwrappedCast<Wrapping<$Dst>> for $Src {
|
||||
#[inline]
|
||||
fn unwrapped_cast(self) -> Wrapping<$Dst> {
|
||||
cast(self)
|
||||
}
|
||||
}
|
||||
)* };
|
||||
}
|
||||
|
||||
float_to_int! { f32, u32, i32 => i8 i16 i32 }
|
||||
float_to_int! { f32, u64, i64 => i64 }
|
||||
float_to_int! { f32, u128, i128 => i128 }
|
||||
#[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))]
|
||||
float_to_int! { f32, u32, i32 => isize }
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
float_to_int! { f32, u64, i64 => isize }
|
||||
float_to_int! { f32, u32, i32 => u8 u16 u32 }
|
||||
float_to_int! { f32, u64, i64 => u64 }
|
||||
float_to_int! { f32, u128, i128 => u128 }
|
||||
#[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))]
|
||||
float_to_int! { f32, u32, i32 => usize }
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
float_to_int! { f32, u64, i64 => usize }
|
||||
|
||||
float_to_int! { f64, u64, i64 => i8 i16 i32 i64 }
|
||||
float_to_int! { f64, u128, i128 => i128 }
|
||||
float_to_int! { f64, u64, i64 => isize }
|
||||
float_to_int! { f64, u64, i64 => u8 u16 u32 u64 }
|
||||
float_to_int! { f64, u128, i128 => u128 }
|
||||
float_to_int! { f64, u64, i64 => usize }
|
||||
|
||||
float_to_int! { Round<f32>, u32, i32 => i8 i16 i32 }
|
||||
float_to_int! { Round<f32>, u64, i64 => i64 }
|
||||
float_to_int! { Round<f32>, u128, i128 => i128 }
|
||||
#[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))]
|
||||
float_to_int! { Round<f32>, u32, i32 => isize }
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
float_to_int! { Round<f32>, u64, i64 => isize }
|
||||
float_to_int! { Round<f32>, u32, i32 => u8 u16 u32 }
|
||||
float_to_int! { Round<f32>, u64, i64 => u64 }
|
||||
float_to_int! { Round<f32>, u128, i128 => u128 }
|
||||
#[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))]
|
||||
float_to_int! { Round<f32>, u32, i32 => usize }
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
float_to_int! { Round<f32>, u64, i64 => usize }
|
||||
|
||||
float_to_int! { Round<f64>, u64, i64 => i8 i16 i32 i64 }
|
||||
float_to_int! { Round<f64>, u128, i128 => i128 }
|
||||
float_to_int! { Round<f64>, u64, i64 => isize }
|
||||
float_to_int! { Round<f64>, u64, i64 => u8 u16 u32 u64 }
|
||||
float_to_int! { Round<f64>, u128, i128 => u128 }
|
||||
float_to_int! { Round<f64>, u64, i64 => usize }
|
||||
|
||||
macro_rules! signed {
|
||||
($($Dst:ty),*) => { $(
|
||||
bool_to_int! { $Dst }
|
||||
same_signedness! { i8, i16, i32, i64, i128, isize => $Dst }
|
||||
unsigned_to_signed! { u8, u16, u32, u64, u128, usize => $Dst }
|
||||
wrapping_int! {
|
||||
bool, i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize => $Dst
|
||||
}
|
||||
)* };
|
||||
}
|
||||
|
||||
macro_rules! unsigned {
|
||||
($($Dst:ty),*) => { $(
|
||||
bool_to_int! { $Dst }
|
||||
signed_to_unsigned! { i8, i16, i32, i64, i128, isize => $Dst }
|
||||
same_signedness! { u8, u16, u32, u64, u128, usize => $Dst }
|
||||
wrapping_int! {
|
||||
bool, i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize => $Dst
|
||||
}
|
||||
)* };
|
||||
}
|
||||
|
||||
signed! { i8, i16, i32, i64, i128, isize }
|
||||
unsigned! { u8, u16, u32, u64, u128, usize }
|
||||
|
||||
enum FloatKind<Uns> {
|
||||
Nan,
|
||||
Infinite,
|
||||
Overflowing(Uns, bool),
|
||||
}
|
||||
struct Float<Uns> {
|
||||
neg: bool,
|
||||
kind: FloatKind<Uns>,
|
||||
}
|
||||
|
||||
macro_rules! from_for_float {
|
||||
($Src:ty, $Uns:ty, $PREC:expr => $($Dst:ty),*) => { $(
|
||||
impl From<$Src> for Float<$Dst> {
|
||||
fn from(src: $Src) -> Self {
|
||||
const SRC_NBITS: i32 = mem::size_of::<$Src>() as i32 * 8;
|
||||
const DST_NBITS: i32 = mem::size_of::<$Dst>() as i32 * 8;
|
||||
const MANT_NBITS: i32 = $PREC - 1;
|
||||
const EXP_NBITS: i32 = SRC_NBITS - MANT_NBITS - 1;
|
||||
const EXP_BIAS: i32 = (1 << (EXP_NBITS - 1)) - 1;
|
||||
const SIGN_MASK: $Uns = !(!0 >> 1);
|
||||
const MANT_MASK: $Uns = !(!0 << MANT_NBITS);
|
||||
const EXP_MASK: $Uns = !(SIGN_MASK | MANT_MASK);
|
||||
|
||||
let u = src.to_bits();
|
||||
let neg = (u & SIGN_MASK) != 0;
|
||||
let biased_exp = u & EXP_MASK;
|
||||
if biased_exp == EXP_MASK {
|
||||
let kind = if (u & MANT_MASK) == 0 {
|
||||
FloatKind::Infinite
|
||||
} else {
|
||||
FloatKind::Nan
|
||||
};
|
||||
return Float { neg, kind };
|
||||
}
|
||||
let shift = (biased_exp >> MANT_NBITS) as i32 - (EXP_BIAS + MANT_NBITS);
|
||||
|
||||
// Check if the magnitude is smaller than one. Do not return
|
||||
// early if shift == -MANT_NBITS, as there is implicit one.
|
||||
if shift < -MANT_NBITS {
|
||||
let kind = FloatKind::Overflowing(0, false);
|
||||
return Float { neg, kind };
|
||||
}
|
||||
|
||||
// Check if the least significant bit will be in a $Dst.
|
||||
if shift >= DST_NBITS {
|
||||
let kind = FloatKind::Overflowing(0, true);
|
||||
return Float { neg, kind };
|
||||
}
|
||||
|
||||
let mut significand: $Dst = (u & MANT_MASK).into();
|
||||
// Add implicit one.
|
||||
significand |= 1 << MANT_NBITS;
|
||||
let kind = if shift < 0 {
|
||||
FloatKind::Overflowing(significand >> -shift, false)
|
||||
} else {
|
||||
let wrapped = significand << shift;
|
||||
let overflow = (wrapped >> shift) != significand;
|
||||
FloatKind::Overflowing(wrapped, overflow)
|
||||
};
|
||||
Float { neg, kind }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Round<$Src>> for Float<$Dst> {
|
||||
fn from(src: Round<$Src>) -> Self {
|
||||
const SRC_NBITS: i32 = mem::size_of::<$Src>() as i32 * 8;
|
||||
const DST_NBITS: i32 = mem::size_of::<$Dst>() as i32 * 8;
|
||||
const MANT_NBITS: i32 = $PREC - 1;
|
||||
const EXP_NBITS: i32 = SRC_NBITS - MANT_NBITS - 1;
|
||||
const EXP_BIAS: i32 = (1 << (EXP_NBITS - 1)) - 1;
|
||||
const SIGN_MASK: $Uns = !(!0 >> 1);
|
||||
const MANT_MASK: $Uns = !(!0 << MANT_NBITS);
|
||||
const EXP_MASK: $Uns = !(SIGN_MASK | MANT_MASK);
|
||||
|
||||
let src = src.0;
|
||||
let u = src.to_bits();
|
||||
let neg = (u & SIGN_MASK) != 0;
|
||||
let biased_exp = u & EXP_MASK;
|
||||
if biased_exp == EXP_MASK {
|
||||
let kind = if (u & MANT_MASK) == 0 {
|
||||
FloatKind::Infinite
|
||||
} else {
|
||||
FloatKind::Nan
|
||||
};
|
||||
return Float { neg, kind };
|
||||
}
|
||||
let shift = (biased_exp >> MANT_NBITS) as i32 - (EXP_BIAS + MANT_NBITS);
|
||||
|
||||
// If shift = -MANT_BITS, then 1 ≤ x < 2.
|
||||
// If shift = -MANT_BITS - 1, then 0.5 ≤ x < 1, which can be rounded up.
|
||||
// If shift < -MANT_BITS - 1, then x < 0.5, which is rounded down.
|
||||
//// || (shift == -MANT_NBITS - 1 && ((u & MANT_MASK) != 0 || x))
|
||||
if shift < -MANT_NBITS - 1 {
|
||||
let kind = FloatKind::Overflowing(0, false);
|
||||
return Float { neg, kind };
|
||||
}
|
||||
|
||||
// Check if the least significant bit will be in a $Dst.
|
||||
if shift >= DST_NBITS {
|
||||
let kind = FloatKind::Overflowing(0, true);
|
||||
return Float { neg, kind };
|
||||
}
|
||||
|
||||
let mut significand: $Dst = (u & MANT_MASK).into();
|
||||
// Add implicit one.
|
||||
significand |= 1 << MANT_NBITS;
|
||||
let kind = if shift < 0 {
|
||||
let right = -shift;
|
||||
let round_bit = 1 << (right - 1);
|
||||
if (significand & round_bit) != 0 && (significand & (3 * round_bit - 1)) != 0 {
|
||||
significand += round_bit;
|
||||
}
|
||||
FloatKind::Overflowing(significand >> right, false)
|
||||
} else {
|
||||
let wrapped = significand << shift;
|
||||
let overflow = (wrapped >> shift) != significand;
|
||||
FloatKind::Overflowing(wrapped, overflow)
|
||||
};
|
||||
Float { neg, kind }
|
||||
}
|
||||
}
|
||||
)* };
|
||||
}
|
||||
|
||||
from_for_float! { f32, u32, 24 => u32, u64, u128 }
|
||||
from_for_float! { f64, u64, 53 => u64, u128 }
|
||||
Vendored
+1135
File diff suppressed because it is too large.
Load diff
Vendored
+1408
File diff suppressed because it is too large.
Load diff
Vendored
+1627
File diff suppressed because it is too large.
Load diff
Vendored
+208
@@ -0,0 +1,208 @@
|
||||
// Copyright © 2019–2021 Trevor Spiteri
|
||||
|
||||
// This library is free software: you can redistribute it and/or
|
||||
// modify it under the terms of either
|
||||
//
|
||||
// * the Apache License, Version 2.0 or
|
||||
// * the MIT License
|
||||
//
|
||||
// at your option.
|
||||
//
|
||||
// You should have recieved copies of the Apache License and the MIT
|
||||
// License along with the library. If not, see
|
||||
// <https://www.apache.org/licenses/LICENSE-2.0> and
|
||||
// <https://opensource.org/licenses/MIT>.
|
||||
|
||||
#![allow(clippy::float_cmp)]
|
||||
|
||||
mod float_to_int;
|
||||
mod int_to_int;
|
||||
mod to_float;
|
||||
|
||||
use crate::{
|
||||
Az, CastFrom, CheckedAs, CheckedCastFrom, OverflowingAs, OverflowingCastFrom, Round,
|
||||
SaturatingAs, SaturatingCastFrom, UnwrappedAs, UnwrappedCastFrom, WrappingAs, WrappingCastFrom,
|
||||
};
|
||||
use core::{
|
||||
f32, f64,
|
||||
fmt::Debug,
|
||||
ops::{Add, Neg, Not, Shl, Shr, Sub},
|
||||
};
|
||||
|
||||
trait Int
|
||||
where
|
||||
Self: Copy + Debug + Default + Ord,
|
||||
Self: Shl<usize, Output = Self> + Shr<usize, Output = Self>,
|
||||
Self: Not<Output = Self> + Add<Output = Self> + Sub<Output = Self>,
|
||||
{
|
||||
#[inline]
|
||||
fn zero() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
#[inline]
|
||||
fn one() -> Self {
|
||||
!(!Self::default() << 1)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Int for I
|
||||
where
|
||||
I: Copy + Debug + Default + Ord,
|
||||
I: Shl<usize, Output = I> + Shr<usize, Output = I>,
|
||||
I: Not<Output = I> + Add<Output = I> + Sub<Output = I>,
|
||||
{
|
||||
}
|
||||
|
||||
trait Float
|
||||
where
|
||||
Self: Copy + Debug + PartialOrd + From<i8>,
|
||||
Self: Neg<Output = Self> + Add<Output = Self> + Sub<Output = Self>,
|
||||
{
|
||||
fn prec() -> usize;
|
||||
fn nan() -> Self;
|
||||
fn inf() -> Self;
|
||||
fn max() -> Self;
|
||||
fn int_shl(int: i8, shl: i8) -> Self;
|
||||
fn to_round(self) -> Round<Self>;
|
||||
}
|
||||
|
||||
impl Float for f32 {
|
||||
fn prec() -> usize {
|
||||
24
|
||||
}
|
||||
fn nan() -> Self {
|
||||
f32::NAN
|
||||
}
|
||||
fn inf() -> Self {
|
||||
f32::INFINITY
|
||||
}
|
||||
fn max() -> Self {
|
||||
f32::MAX
|
||||
}
|
||||
fn int_shl(int: i8, shl: i8) -> Self {
|
||||
f32::from(int) * f32::from(shl).exp2()
|
||||
}
|
||||
fn to_round(self) -> Round<f32> {
|
||||
Round(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Float for f64 {
|
||||
fn prec() -> usize {
|
||||
53
|
||||
}
|
||||
fn nan() -> Self {
|
||||
f64::NAN
|
||||
}
|
||||
fn inf() -> Self {
|
||||
f64::INFINITY
|
||||
}
|
||||
fn max() -> Self {
|
||||
f64::MAX
|
||||
}
|
||||
fn int_shl(int: i8, shl: i8) -> Self {
|
||||
f64::from(int) * f64::from(shl).exp2()
|
||||
}
|
||||
fn to_round(self) -> Round<f64> {
|
||||
Round(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from() {
|
||||
assert_eq!(<i8 as CastFrom<u8>>::cast_from(1u8), 1i8);
|
||||
assert_eq!(
|
||||
<i8 as CheckedCastFrom<u8>>::checked_cast_from(1u8),
|
||||
Some(1i8)
|
||||
);
|
||||
assert_eq!(<i8 as CheckedCastFrom<u8>>::checked_cast_from(255u8), None);
|
||||
assert_eq!(
|
||||
<i8 as SaturatingCastFrom<u8>>::saturating_cast_from(1u8),
|
||||
1i8
|
||||
);
|
||||
assert_eq!(
|
||||
<i8 as SaturatingCastFrom<u8>>::saturating_cast_from(255u8),
|
||||
127i8
|
||||
);
|
||||
assert_eq!(<i8 as WrappingCastFrom<u8>>::wrapping_cast_from(1u8), 1i8);
|
||||
assert_eq!(
|
||||
<i8 as WrappingCastFrom<u8>>::wrapping_cast_from(255u8),
|
||||
-1i8
|
||||
);
|
||||
assert_eq!(
|
||||
<i8 as OverflowingCastFrom<u8>>::overflowing_cast_from(1u8),
|
||||
(1i8, false)
|
||||
);
|
||||
assert_eq!(
|
||||
<i8 as OverflowingCastFrom<u8>>::overflowing_cast_from(255u8),
|
||||
(-1i8, true)
|
||||
);
|
||||
assert_eq!(<i8 as UnwrappedCastFrom<u8>>::unwrapped_cast_from(1u8), 1i8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn az() {
|
||||
assert_eq!(1.az::<u8>(), 1);
|
||||
assert_eq!((-1).checked_as::<u8>(), None);
|
||||
assert_eq!(1.checked_as::<u8>(), Some(1));
|
||||
assert_eq!((-1).saturating_as::<u8>(), 0);
|
||||
assert_eq!(1000.saturating_as::<u8>(), 255);
|
||||
assert_eq!((-1).wrapping_as::<u8>(), 255);
|
||||
assert_eq!((-1).overflowing_as::<u8>(), (255, true));
|
||||
assert_eq!(1.overflowing_as::<u8>(), (1, false));
|
||||
assert_eq!(1.unwrapped_as::<u8>(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn borrow_as() {
|
||||
use crate::{Cast, CheckedCast, OverflowingCast, SaturatingCast, UnwrappedCast, WrappingCast};
|
||||
use core::borrow::Borrow;
|
||||
|
||||
struct I(i32);
|
||||
impl Cast<u32> for &'_ I {
|
||||
fn cast(self) -> u32 {
|
||||
self.0.cast()
|
||||
}
|
||||
}
|
||||
impl CheckedCast<u32> for &'_ I {
|
||||
fn checked_cast(self) -> Option<u32> {
|
||||
self.0.checked_cast()
|
||||
}
|
||||
}
|
||||
impl SaturatingCast<u32> for &'_ I {
|
||||
fn saturating_cast(self) -> u32 {
|
||||
self.0.saturating_cast()
|
||||
}
|
||||
}
|
||||
impl WrappingCast<u32> for &'_ I {
|
||||
fn wrapping_cast(self) -> u32 {
|
||||
self.0.wrapping_cast()
|
||||
}
|
||||
}
|
||||
impl OverflowingCast<u32> for &'_ I {
|
||||
fn overflowing_cast(self) -> (u32, bool) {
|
||||
self.0.overflowing_cast()
|
||||
}
|
||||
}
|
||||
impl UnwrappedCast<u32> for &'_ I {
|
||||
fn unwrapped_cast(self) -> u32 {
|
||||
self.0.unwrapped_cast()
|
||||
}
|
||||
}
|
||||
|
||||
let r = &I(12);
|
||||
assert_eq!(r.borrow().az::<u32>(), 12);
|
||||
assert_eq!(r.borrow().checked_as::<u32>(), Some(12));
|
||||
assert_eq!(r.borrow().saturating_as::<u32>(), 12);
|
||||
assert_eq!(r.borrow().borrow().wrapping_as::<u32>(), 12);
|
||||
assert_eq!(r.borrow().overflowing_as::<u32>(), (12, false));
|
||||
assert_eq!(r.borrow().unwrapped_as::<u32>(), 12);
|
||||
let r = &I(-5);
|
||||
assert_eq!(r.borrow().checked_as::<u32>(), None);
|
||||
assert_eq!(r.borrow().saturating_as::<u32>(), 0);
|
||||
assert_eq!(r.borrow().wrapping_as::<u32>(), 5u32.wrapping_neg());
|
||||
assert_eq!(
|
||||
r.borrow().overflowing_as::<u32>(),
|
||||
(5u32.wrapping_neg(), true)
|
||||
);
|
||||
}
|
||||
Vendored
+148
@@ -0,0 +1,148 @@
|
||||
// Copyright © 2019–2021 Trevor Spiteri
|
||||
|
||||
// This library is free software: you can redistribute it and/or
|
||||
// modify it under the terms of either
|
||||
//
|
||||
// * the Apache License, Version 2.0 or
|
||||
// * the MIT License
|
||||
//
|
||||
// at your option.
|
||||
//
|
||||
// You should have recieved copies of the Apache License and the MIT
|
||||
// License along with the library. If not, see
|
||||
// <https://www.apache.org/licenses/LICENSE-2.0> and
|
||||
// <https://opensource.org/licenses/MIT>.
|
||||
|
||||
use crate::{cast, checked_cast, unwrapped_cast};
|
||||
use core::{f32, f64};
|
||||
|
||||
macro_rules! slice_to_float {
|
||||
($vals: expr) => {
|
||||
for &val in $vals {
|
||||
if val != val {
|
||||
assert!(cast::<_, f32>(val).is_nan());
|
||||
assert!(checked_cast::<_, f32>(val).unwrap().is_nan());
|
||||
assert!(unwrapped_cast::<_, f32>(val).is_nan());
|
||||
assert!(cast::<_, f64>(val).is_nan());
|
||||
assert!(checked_cast::<_, f64>(val).unwrap().is_nan());
|
||||
assert!(unwrapped_cast::<_, f64>(val).is_nan());
|
||||
} else {
|
||||
assert_eq!(cast::<_, f32>(val), val as f32);
|
||||
assert_eq!(checked_cast::<_, f32>(val), Some(val as f32));
|
||||
assert_eq!(unwrapped_cast::<_, f32>(val), val as f32);
|
||||
assert_eq!(cast::<_, f64>(val), val as f64);
|
||||
assert_eq!(checked_cast::<_, f64>(val), Some(val as f64));
|
||||
assert_eq!(unwrapped_cast::<_, f64>(val), val as f64);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_float() {
|
||||
let small_i = &[
|
||||
i8::min_value(),
|
||||
i8::min_value() + 1,
|
||||
-1,
|
||||
0,
|
||||
1,
|
||||
i8::max_value() - 1,
|
||||
i8::max_value(),
|
||||
];
|
||||
let small_u = &[0, 1, u8::max_value() - 1, u8::max_value()];
|
||||
let small_f = &[
|
||||
0.0,
|
||||
f32::MIN_POSITIVE,
|
||||
1.0,
|
||||
1.5,
|
||||
2.0,
|
||||
f32::MAX,
|
||||
f32::INFINITY,
|
||||
f32::NAN,
|
||||
];
|
||||
let big_i = &[
|
||||
i128::min_value(),
|
||||
i128::min_value() + 1,
|
||||
-1,
|
||||
0,
|
||||
1,
|
||||
i128::max_value() - 1,
|
||||
i128::max_value(),
|
||||
];
|
||||
let big_u = &[0, 1, u128::max_value() - 1, u128::max_value()];
|
||||
let big_f = &[
|
||||
0.0,
|
||||
f64::MIN_POSITIVE,
|
||||
1.0,
|
||||
1.5,
|
||||
2.0,
|
||||
f64::MAX,
|
||||
f64::INFINITY,
|
||||
f64::NAN,
|
||||
];
|
||||
|
||||
slice_to_float!(small_i);
|
||||
slice_to_float!(small_u);
|
||||
slice_to_float!(small_f);
|
||||
slice_to_float!(big_i);
|
||||
slice_to_float!(big_u);
|
||||
slice_to_float!(big_f);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn specific_to_float() {
|
||||
// 0XFFFF_FE80 in 24 bits of precision becomes 0xFFFF_FE00
|
||||
assert_eq!(
|
||||
cast::<_, f32>(0xFFFF_FE80u32),
|
||||
(24f32.exp2() - 2.0) * 8f32.exp2()
|
||||
);
|
||||
// 0XFFFF_FE80..=0xFFFF_FF7F in 24 bits of precision become 0xFFFF_FF00
|
||||
assert_eq!(
|
||||
cast::<_, f32>(0xFFFF_FE81u32),
|
||||
(24f32.exp2() - 1.0) * 8f32.exp2()
|
||||
);
|
||||
assert_eq!(
|
||||
cast::<_, f32>(0xFFFF_FF00u32),
|
||||
(24f32.exp2() - 1.0) * 8f32.exp2()
|
||||
);
|
||||
assert_eq!(
|
||||
cast::<_, f32>(0xFFFF_FF7Fu32),
|
||||
(24f32.exp2() - 1.0) * 8f32.exp2()
|
||||
);
|
||||
// 0xFFFF_FF80 in 24 bits of precision becoms 0x1_0000_0000
|
||||
assert_eq!(cast::<_, f32>(0xFFFF_FF80u32), 24f32.exp2() * 8f32.exp2());
|
||||
assert_eq!(cast::<_, f32>(0xFFFF_FFFFu32), 24f32.exp2() * 8f32.exp2());
|
||||
|
||||
// Same as above, but source is 128-bit instead of 32-bit.
|
||||
// * (24f32.exp2() - 1.0) * 104f32.exp2() == f32::MAX
|
||||
// * 24f32.exp2() * 104f32.exp2() > f32::MAX, overflows to f32::INFINITY
|
||||
assert_eq!(
|
||||
cast::<_, f32>(0xFFFF_FE80_0000_0000_0000_0000_0000_0000u128),
|
||||
(24f32.exp2() - 2.0) * 104f32.exp2()
|
||||
);
|
||||
assert_eq!(
|
||||
cast::<_, f32>(0xFFFF_FE80_0000_0000_0000_0000_0000_0001u128),
|
||||
f32::MAX
|
||||
);
|
||||
assert_eq!(
|
||||
cast::<_, f32>(0xFFFF_FF00_0000_0000_0000_0000_0000_0000u128),
|
||||
f32::MAX
|
||||
);
|
||||
assert_eq!(
|
||||
cast::<_, f32>(0xFFFF_FF7F_FFFF_FFFF_FFFF_FFFF_FFFF_FFFFu128),
|
||||
f32::MAX
|
||||
);
|
||||
assert_eq!(
|
||||
cast::<_, f32>(0xFFFF_FF80_0000_0000_0000_0000_0000_0000u128),
|
||||
f32::INFINITY
|
||||
);
|
||||
assert_eq!(
|
||||
cast::<_, f32>(0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFFu128),
|
||||
f32::INFINITY
|
||||
);
|
||||
|
||||
assert_eq!(cast::<_, f32>(f64::MAX), f32::INFINITY);
|
||||
assert_eq!(cast::<_, f32>(-f64::MAX), f32::NEG_INFINITY);
|
||||
assert_eq!(cast::<_, f32>(-f64::MAX), f32::NEG_INFINITY);
|
||||
assert_eq!(cast::<_, f32>(f64::from(f32::MAX) + 1.0), f32::MAX);
|
||||
}
|
||||
Reference in new issue
Block a user