use crate::error::{StatorError, StatorResult};
use crate::objects::value::JsValue;
pub(crate) const MAX_ALLOCATION_LENGTH: usize = 1 << 28;
pub(crate) fn clamped_f64_to_usize(n: f64) -> usize {
if n.is_nan() || n < 0.0 || n.is_infinite() {
return 0;
}
let clamped = n.min(MAX_ALLOCATION_LENGTH as f64);
clamped as usize
}
pub(crate) fn checked_f64_to_length(n: f64) -> StatorResult<usize> {
if n.is_nan() || n.is_infinite() || n < 0.0 {
return Err(StatorError::RangeError(
"Invalid array/buffer length".to_string(),
));
}
let truncated = n.floor();
if truncated > MAX_ALLOCATION_LENGTH as f64 {
return Err(StatorError::RangeError(
"Invalid array/buffer length".to_string(),
));
}
Ok(truncated as usize)
}
pub(crate) fn checked_f64_to_index(n: f64) -> StatorResult<usize> {
if n.is_nan() {
return Ok(0);
}
if n.is_infinite() || n < 0.0 {
return Err(StatorError::RangeError("Invalid index".to_string()));
}
let truncated = n.floor();
if truncated > MAX_ALLOCATION_LENGTH as f64 {
return Err(StatorError::RangeError("Invalid index".to_string()));
}
Ok(truncated as usize)
}
pub(crate) fn same_value_zero(a: &JsValue, b: &JsValue) -> bool {
a.same_value_zero(b)
}
pub(crate) fn i64_to_radix_string(value: i64, radix: u32) -> String {
if radix == 10 {
return value.to_string();
}
if value == 0 {
return "0".to_string();
}
let negative = value < 0;
let mut n = if negative {
-(value as i128)
} else {
value as i128
};
let digits = b"0123456789abcdefghijklmnopqrstuvwxyz";
let mut buf = Vec::new();
let r = radix as i128;
while n > 0 {
buf.push(digits[(n % r) as usize]);
n /= r;
}
if negative {
buf.push(b'-');
}
buf.reverse();
String::from_utf8(buf).unwrap_or_default()
}
pub(crate) fn f64_to_radix_string(value: f64, radix: u32) -> String {
if value.is_nan() {
return "NaN".to_string();
}
if value.is_infinite() {
return if value > 0.0 {
"Infinity".to_string()
} else {
"-Infinity".to_string()
};
}
if value == 0.0 {
return "0".to_string();
}
if value.fract() == 0.0 && value.abs() < (i64::MAX as f64) {
return i64_to_radix_string(value as i64, radix);
}
format!("{value}")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_same_value_zero_nan_equals_nan() {
assert!(same_value_zero(
&JsValue::HeapNumber(f64::NAN),
&JsValue::HeapNumber(f64::NAN)
));
}
#[test]
fn test_same_value_zero_negative_zero_equals_positive_zero() {
assert!(same_value_zero(
&JsValue::HeapNumber(0.0_f64),
&JsValue::HeapNumber(-0.0_f64)
));
}
#[test]
fn test_same_value_zero_distinct_numbers() {
assert!(!same_value_zero(
&JsValue::HeapNumber(1.0),
&JsValue::HeapNumber(2.0)
));
}
#[test]
fn test_same_value_zero_primitives() {
assert!(same_value_zero(&JsValue::Smi(42), &JsValue::Smi(42)));
assert!(!same_value_zero(&JsValue::Smi(1), &JsValue::Smi(2)));
assert!(same_value_zero(&JsValue::Null, &JsValue::Null));
assert!(!same_value_zero(&JsValue::Null, &JsValue::Undefined));
}
#[test]
fn test_clamped_f64_to_usize_normal() {
assert_eq!(clamped_f64_to_usize(0.0), 0);
assert_eq!(clamped_f64_to_usize(42.9), 42);
assert_eq!(clamped_f64_to_usize(100.0), 100);
}
#[test]
fn test_clamped_f64_to_usize_edge_cases() {
assert_eq!(clamped_f64_to_usize(f64::NAN), 0);
assert_eq!(clamped_f64_to_usize(f64::INFINITY), 0);
assert_eq!(clamped_f64_to_usize(f64::NEG_INFINITY), 0);
assert_eq!(clamped_f64_to_usize(-1.0), 0);
}
#[test]
fn test_clamped_f64_to_usize_huge_value() {
assert_eq!(
clamped_f64_to_usize(7_881_299_347_898_368.0),
MAX_ALLOCATION_LENGTH,
);
}
#[test]
fn test_checked_f64_to_length_normal() {
assert_eq!(checked_f64_to_length(0.0).unwrap(), 0);
assert_eq!(checked_f64_to_length(10.0).unwrap(), 10);
}
#[test]
fn test_checked_f64_to_length_rejects_bad_values() {
assert!(checked_f64_to_length(f64::NAN).is_err());
assert!(checked_f64_to_length(f64::INFINITY).is_err());
assert!(checked_f64_to_length(-1.0).is_err());
assert!(checked_f64_to_length(5_000_000_000_000.0).is_err());
}
}