use bumpalo::Bump;
use datavalue::NumberValue;
use super::DataValue;
pub(crate) fn data_to_str<'a>(v: &DataValue<'a>, arena: &'a Bump) -> &'a str {
match v {
DataValue::String(s) => s,
DataValue::Null => "",
DataValue::Bool(true) => "true",
DataValue::Bool(false) => "false",
DataValue::Number(NumberValue::Integer(i)) => {
arena.alloc_str(itoa::Buffer::new().format(*i))
}
DataValue::Number(NumberValue::Float(f)) => float_to_str(*f, arena),
other => arena.alloc_str(&other.to_string()),
}
}
fn float_to_str(f: f64, arena: &Bump) -> &str {
if f.is_nan() || f.is_infinite() {
return "null";
}
if f.fract() == 0.0 {
if f >= i64::MIN as f64 && f < i64::MAX as f64 {
let digits_buf = &mut itoa::Buffer::new();
let digits = digits_buf.format(f as i64);
let mut buf = bumpalo::collections::String::with_capacity_in(digits.len() + 2, arena);
buf.push_str(digits);
buf.push_str(".0");
return buf.into_bump_str();
}
use std::fmt::Write as _;
let mut buf = bumpalo::collections::String::with_capacity_in(24, arena);
let _ = write!(&mut buf, "{f:?}");
return buf.into_bump_str();
}
let ryu_buf = &mut ryu::Buffer::new();
let s = ryu_buf.format_finite(f);
if ryu_matches_display(s) {
arena.alloc_str(s)
} else {
use std::fmt::Write as _;
let mut buf = bumpalo::collections::String::with_capacity_in(24, arena);
let _ = write!(&mut buf, "{f}");
buf.into_bump_str()
}
}
#[inline]
fn ryu_matches_display(s: &str) -> bool {
let mut sig = 0usize;
let mut seen_nonzero = false;
for &b in s.as_bytes() {
match b {
b'e' | b'E' => return false,
b'1'..=b'9' => {
seen_nonzero = true;
sig += 1;
}
b'0' if seen_nonzero => sig += 1,
_ => {}
}
}
sig <= 15
}
#[inline(always)]
pub(crate) fn truthy_arena(v: &DataValue<'_>, engine: &crate::Engine) -> bool {
use crate::config::TruthyEvaluator;
match &engine.config().truthy_evaluator {
TruthyEvaluator::JavaScript | TruthyEvaluator::Python => super::truthy_js_arena(v),
TruthyEvaluator::StrictBoolean => match v {
DataValue::Null => false,
DataValue::Bool(b) => *b,
_ => true,
},
TruthyEvaluator::Custom(f) => f(&v.to_owned()),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn reference(n: NumberValue) -> String {
format!("{n}")
}
fn assert_parity_i64(i: i64) {
let arena = Bump::new();
let v = DataValue::Number(NumberValue::Integer(i));
assert_eq!(
data_to_str(&v, &arena),
reference(NumberValue::Integer(i)),
"integer parity broke for {i}"
);
}
fn assert_parity_f64(f: f64) {
let arena = Bump::new();
let v = DataValue::Number(NumberValue::Float(f));
assert_eq!(
data_to_str(&v, &arena),
reference(NumberValue::Float(f)),
"float parity broke for {f:?} (bits {:#x})",
f.to_bits()
);
}
struct XorShift(u64);
impl XorShift {
fn next(&mut self) -> u64 {
let mut x = self.0;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
self.0 = x;
x
}
}
#[test]
fn integer_display_parity() {
for i in [
i64::MIN,
i64::MIN + 1,
-10,
-1,
0,
1,
42,
i64::MAX - 1,
i64::MAX,
] {
assert_parity_i64(i);
}
let mut rng = XorShift(0x9E37_79B9_7F4A_7C15);
for _ in 0..100_000 {
assert_parity_i64(rng.next() as i64);
}
}
#[test]
fn float_display_parity() {
let corpus: &[f64] = &[
0.0,
-0.0,
1.0,
-1.0,
1.5,
-1.5,
0.1,
0.3,
123.456,
1e300,
-1e300,
9223372036854775808.0,
9223372036854774784.0,
-9223372036854775808.0,
-9223372036854777856.0,
1e-7,
-1e-7,
1e-5,
1e-6,
9.999999999999999e-6,
0.30000000000000004,
123456789012345680000.0,
4503599627370495.5,
-4503599627370495.5,
4503599627370496.0,
1e15 + 0.5,
1e16,
f64::MAX,
f64::MIN,
f64::MIN_POSITIVE,
5e-324,
f64::NAN,
f64::INFINITY,
f64::NEG_INFINITY,
f64::from_bits(0x4314_6906_eff8_d6c1),
f64::from_bits(0xc304_0c93_6651_e66a),
];
for &f in corpus {
assert_parity_f64(f);
}
let mut rng = XorShift(0xD1B5_4A32_D192_ED03);
for _ in 0..100_000 {
let m = (rng.next() % 1_000_000_000_000) as f64;
let k = (rng.next() % 9 + 1) as i32;
let sign = if rng.next() & 1 == 0 { 1.0 } else { -1.0 };
assert_parity_f64(m / 10f64.powi(k) * sign);
}
for _ in 0..100_000 {
assert_parity_f64(f64::from_bits(rng.next()));
}
let base = 4503599627370496.0f64.to_bits();
for i in 0..100_000u64 {
assert_parity_f64(f64::from_bits(base - 1 - i * 87));
}
}
}