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:
Jagoda Estera Ślązak
2025-05-13 19:37:18 +02:00
committed by GitHub
parent 54bb651008
commit 00da8c6203
10 changed files with 191 additions and 5 deletions

4
Cargo.lock generated
View File

@@ -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"

View File

@@ -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 {

View File

@@ -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"]

View File

@@ -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;

View File

@@ -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)]

View File

@@ -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.
///

View File

@@ -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")]

View 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);
}
}
}

View File

@@ -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

View File

@@ -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.