Files
isokit/crates/isokit-core/src/fmt.rs
T

110 lines
2.8 KiB
Rust

//! Number and text formatting shared by the SVG writer and the scripts.
use std::fmt::Write;
/// Append `v` rounded to 0.1 the way the JS views did (`Math.round(v * 10) / 10`):
/// halves round up, no trailing `.0`, never `-0`. Non-finite values print as
/// `NaN` so the checks can find them in the output.
pub fn r1(out: &mut String, v: f64) {
if !v.is_finite() {
out.push_str("NaN");
return;
}
let t = (v * 10.0 + 0.5).floor() as i64;
if t < 0 {
out.push('-');
}
let a = t.unsigned_abs();
let _ = write!(out, "{}", a / 10);
if !a.is_multiple_of(10) {
let _ = write!(out, ".{}", a % 10);
}
}
/// [`r1`] into a new string.
pub fn r1s(v: f64) -> String {
let mut s = String::new();
r1(&mut s, v);
s
}
/// Round to 0.1 as a number.
pub fn round1(v: f64) -> f64 {
(v * 10.0 + 0.5).floor() / 10.0
}
/// Escape text for SVG/HTML content and attribute values.
pub fn esc(s: &str) -> String {
let mut o = String::with_capacity(s.len());
for ch in s.chars() {
match ch {
'&' => o.push_str("&amp;"),
'<' => o.push_str("&lt;"),
'>' => o.push_str("&gt;"),
'"' => o.push_str("&quot;"),
c => o.push(c),
}
}
o
}
/// Deterministic pseudo-random number in [0, 1) from any number (the views'
/// `hash(i)`): scenes use it to vary trees, cars and delays without changing
/// from one build to the next.
pub fn hash(i: f64) -> f64 {
let x = (i * 12.9898 + 78.233).sin() * 43758.5453;
x - x.floor()
}
/// Whole number with thousands separators: 12345.6 -> "12,346".
pub fn thousands(n: f64) -> String {
if !n.is_finite() {
return "NaN".into();
}
let v = (n + 0.5).floor() as i64;
let s = v.unsigned_abs().to_string();
let mut o = String::new();
for (i, ch) in s.chars().enumerate() {
if i > 0 && (s.len() - i).is_multiple_of(3) {
o.push(',');
}
o.push(ch);
}
if v < 0 {
format!("-{o}")
} else {
o
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rounding_matches_js() {
assert_eq!(r1s(12.34), "12.3");
assert_eq!(r1s(12.35), "12.4");
assert_eq!(r1s(12.0), "12");
assert_eq!(r1s(-0.04), "0");
assert_eq!(r1s(-2.25), "-2.2"); // JS Math.round(-22.5) = -22
assert_eq!(r1s(-7.0), "-7");
assert_eq!(r1s(f64::NAN), "NaN");
}
#[test]
fn thousands_sep() {
assert_eq!(thousands(1234567.4), "1,234,567");
assert_eq!(thousands(999.0), "999");
assert_eq!(thousands(-1500.0), "-1,500");
}
#[test]
fn hash_is_stable() {
let a = hash(3.0);
assert!((0.0..1.0).contains(&a));
assert_eq!(a, hash(3.0));
assert_ne!(a, hash(4.0));
}
}