fix(no_std): provide f64 polyfills for no_std compatibility (#1840)
Related: https://github.com/rust-lang/rust/issues/137578
This commit is contained in:
committed by
GitHub
parent
54bb651008
commit
00da8c6203
4
Cargo.lock
generated
4
Cargo.lock
generated
@@ -1698,9 +1698,9 @@ checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.11"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
|
||||
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
|
||||
|
||||
[[package]]
|
||||
name = "libmath"
|
||||
|
||||
@@ -985,6 +985,20 @@ fn configure_fill_constraints(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Used instead of `f64::round` directly, to provide fallback for `no_std`.
|
||||
#[cfg(feature = "std")]
|
||||
#[inline]
|
||||
fn round(value: f64) -> f64 {
|
||||
value.round()
|
||||
}
|
||||
|
||||
// A rounding fallback for `no_std` in pure rust.
|
||||
#[cfg(not(feature = "std"))]
|
||||
#[inline]
|
||||
fn round(value: f64) -> f64 {
|
||||
(value + 0.5f64.copysign(value)) as i64 as f64
|
||||
}
|
||||
|
||||
fn changes_to_rects(
|
||||
changes: &HashMap<Variable, f64>,
|
||||
elements: &[Element],
|
||||
@@ -997,8 +1011,8 @@ fn changes_to_rects(
|
||||
.map(|element| {
|
||||
let start = changes.get(&element.start).unwrap_or(&0.0);
|
||||
let end = changes.get(&element.end).unwrap_or(&0.0);
|
||||
let start = (start.round() / FLOAT_PRECISION_MULTIPLIER).round() as u16;
|
||||
let end = (end.round() / FLOAT_PRECISION_MULTIPLIER).round() as u16;
|
||||
let start = round(round(*start) / FLOAT_PRECISION_MULTIPLIER) as u16;
|
||||
let end = round(round(*end) / FLOAT_PRECISION_MULTIPLIER) as u16;
|
||||
let size = end.saturating_sub(start);
|
||||
match direction {
|
||||
Direction::Horizontal => Rect {
|
||||
|
||||
@@ -24,6 +24,9 @@ rustdoc-args = ["--cfg", "docsrs"]
|
||||
[features]
|
||||
default = ["all-widgets"]
|
||||
|
||||
## enables std
|
||||
std = []
|
||||
|
||||
## enables serialization and deserialization of style and color types using the [`serde`] crate.
|
||||
## This is useful if you want to save themes to a file.
|
||||
serde = ["dep:serde", "ratatui-core/serde"]
|
||||
|
||||
@@ -34,6 +34,8 @@ pub use self::map::{Map, MapResolution};
|
||||
pub use self::points::Points;
|
||||
pub use self::rectangle::Rectangle;
|
||||
use crate::block::{Block, BlockExt};
|
||||
#[cfg(not(feature = "std"))]
|
||||
use crate::polyfills::F64Polyfills;
|
||||
|
||||
mod circle;
|
||||
mod line;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use ratatui_core::style::Color;
|
||||
|
||||
use crate::canvas::{Painter, Shape};
|
||||
#[cfg(not(feature = "std"))]
|
||||
use crate::polyfills::F64Polyfills;
|
||||
|
||||
/// A circle with a given center and radius and with a given color
|
||||
#[derive(Debug, Default, Clone, PartialEq)]
|
||||
|
||||
@@ -9,6 +9,8 @@ use ratatui_core::text::{Line, Span};
|
||||
use ratatui_core::widgets::Widget;
|
||||
|
||||
use crate::block::{Block, BlockExt};
|
||||
#[cfg(not(feature = "std"))]
|
||||
use crate::polyfills::F64Polyfills;
|
||||
|
||||
/// A widget to display a progress bar.
|
||||
///
|
||||
|
||||
@@ -86,6 +86,8 @@
|
||||
#![warn(clippy::alloc_instead_of_core)]
|
||||
|
||||
extern crate alloc;
|
||||
#[cfg(feature = "std")]
|
||||
extern crate std;
|
||||
|
||||
pub mod barchart;
|
||||
pub mod block;
|
||||
@@ -103,6 +105,8 @@ pub mod sparkline;
|
||||
pub mod table;
|
||||
pub mod tabs;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
mod polyfills;
|
||||
mod reflow;
|
||||
|
||||
#[cfg(feature = "calendar")]
|
||||
|
||||
156
ratatui-widgets/src/polyfills.rs
Normal file
156
ratatui-widgets/src/polyfills.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
//! Polyfills providing pure rust fallback implementations of various `f64` methods for `no_std`
|
||||
//! compatibility. These implementations may not be as accurate as their built-in counterparts.
|
||||
//! This must be taken into account when using floating point math in this crate.
|
||||
//!
|
||||
//! Implementations based on [`micromath`](https://github.com/tarcieri/micromath) crate.
|
||||
//!
|
||||
//! Related Rust tracking issues:
|
||||
//!
|
||||
//! - <https://github.com/rust-lang/rust/issues/50145>
|
||||
//! - <https://github.com/rust-lang/rust/issues/137578>
|
||||
use core::f64::consts::{FRAC_1_PI, PI};
|
||||
|
||||
#[inline]
|
||||
fn mul_add(val: f64, a: f64, b: f64) -> f64 {
|
||||
val * a + b
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn round(val: f64) -> f64 {
|
||||
(val + 0.5f64.copysign(val)) as i64 as f64
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn floor(val: f64) -> f64 {
|
||||
let mut res = (val as i64) as f64;
|
||||
if val < res {
|
||||
res -= 1.0;
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sin(val: f64) -> f64 {
|
||||
cos(val - PI / 2.0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn cos(val: f64) -> f64 {
|
||||
let mut x = val;
|
||||
x *= FRAC_1_PI / 2.0;
|
||||
x -= 0.25 + floor(x + 0.25);
|
||||
x *= 16.0 * (x.abs() - 0.5);
|
||||
x += 0.225 * x * (x.abs() - 1.0);
|
||||
x
|
||||
}
|
||||
|
||||
pub(crate) trait F64Polyfills {
|
||||
/// Computes `(self * a) + b`.
|
||||
fn mul_add(self, a: f64, b: f64) -> f64;
|
||||
|
||||
/// Returns the nearest integer to `self`. If a value is half-way between two integers, round
|
||||
/// away from `0.0`.
|
||||
fn round(self) -> f64;
|
||||
|
||||
/// Returns the largest integer less than or equal to `self`.
|
||||
fn floor(self) -> f64;
|
||||
|
||||
/// Approximates the sine of a number (in radians) with max error of `0.002`.
|
||||
fn sin(self) -> f64;
|
||||
|
||||
/// Approximates the cosine of a number (in radians) with max error of `0.002`.
|
||||
fn cos(self) -> f64;
|
||||
}
|
||||
|
||||
impl F64Polyfills for f64 {
|
||||
#[inline]
|
||||
fn mul_add(self, a: f64, b: f64) -> f64 {
|
||||
mul_add(self, a, b)
|
||||
}
|
||||
#[inline]
|
||||
fn round(self) -> f64 {
|
||||
round(self)
|
||||
}
|
||||
#[inline]
|
||||
fn floor(self) -> f64 {
|
||||
floor(self)
|
||||
}
|
||||
#[inline]
|
||||
fn sin(self) -> f64 {
|
||||
sin(self)
|
||||
}
|
||||
#[inline]
|
||||
fn cos(self) -> f64 {
|
||||
cos(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use core::f64::consts::{FRAC_PI_2, PI, TAU};
|
||||
|
||||
use super::*;
|
||||
// explicitly use std to prevent testing against itself
|
||||
extern crate std;
|
||||
|
||||
const TEST_VALUES: [f64; 24] = [
|
||||
0.0, 0.5, -0.5, 1.0, -1.0, PI, -PI, FRAC_PI_2, -FRAC_PI_2, TAU, -TAU, 9.4248, -9.4248,
|
||||
0.2528, -7.7047, -1.1596, -2.6095, 6.8435, 3.5392, 5.9725, 0.9172, 9.3539, 2.8843, -1.8483,
|
||||
];
|
||||
|
||||
const MAX_ERROR: f64 = 0.000_000_000_000_01;
|
||||
const TRIG_MAX_ERROR: f64 = 0.002;
|
||||
|
||||
fn assert_with_error(computed: f64, expected: f64, max_error: f64) {
|
||||
let delta = (computed - expected).abs();
|
||||
assert!(
|
||||
delta <= max_error,
|
||||
"error exceeded max value of {max_error}: {computed} vs {expected}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f64_mul_add() {
|
||||
for chunk in TEST_VALUES.chunks(3) {
|
||||
let expected = chunk[0].mul_add(chunk[1], chunk[2]);
|
||||
let computed = mul_add(chunk[0], chunk[1], chunk[2]);
|
||||
assert_with_error(computed, expected, MAX_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f64_round() {
|
||||
for value in TEST_VALUES {
|
||||
let expected = value.round();
|
||||
let computed = round(value);
|
||||
assert_with_error(computed, expected, MAX_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f64_floor() {
|
||||
for value in TEST_VALUES {
|
||||
let expected = value.floor();
|
||||
let computed = floor(value);
|
||||
assert_with_error(computed, expected, MAX_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f64_sin() {
|
||||
for value in TEST_VALUES {
|
||||
let expected = value.sin();
|
||||
let computed = sin(value);
|
||||
assert_with_error(computed, expected, TRIG_MAX_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f64_cos() {
|
||||
for value in TEST_VALUES {
|
||||
let expected = value.cos();
|
||||
let computed = cos(value);
|
||||
assert_with_error(computed, expected, TRIG_MAX_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,9 @@ use ratatui_core::widgets::StatefulWidget;
|
||||
use strum::{Display, EnumString};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use crate::polyfills::F64Polyfills;
|
||||
|
||||
/// A widget to display a scrollbar
|
||||
///
|
||||
/// The following components of the scrollbar are customizable in symbol and style. Note the
|
||||
|
||||
@@ -38,7 +38,7 @@ termion = ["std", "dep:ratatui-termion"]
|
||||
termwiz = ["std", "dep:ratatui-termwiz"]
|
||||
|
||||
## enables std
|
||||
std = ["ratatui-core/std"]
|
||||
std = ["ratatui-core/std", "ratatui-widgets/std"]
|
||||
|
||||
#! The following optional features are available for all backends:
|
||||
## enables serialization and deserialization of style and color types using the [`serde`] crate.
|
||||
|
||||
Reference in New Issue
Block a user