use super::*;
pub(super) fn js_round(x: f64) -> f64 {
if !x.is_finite() {
return x;
}
let floor = x.floor();
let diff = x - floor;
if diff >= 0.5 {
floor + 1.0
} else {
floor
}
}
pub(super) fn round_to_decimals(number: f64, decimals: i32) -> f64 {
let k = 10f64.powi(decimals);
js_round(number * k) / k
}
pub(super) fn js_sign(x: f64) -> f64 {
if x.is_nan() || x == 0.0 {
x
} else if x > 0.0 {
1.0
} else {
-1.0
}
}
pub(super) fn js_parse_float(value: Option<&Value>) -> f64 {
match value {
Some(Value::Number(number)) => number.as_f64().unwrap_or(f64::NAN),
Some(Value::String(text)) => parse_float_prefix(text),
_ => f64::NAN,
}
}
pub(super) fn parse_float_prefix(text: &str) -> f64 {
let trimmed = text.trim_start();
let bytes = trimmed.as_bytes();
let mut end = 0usize;
let mut seen_digit = false;
let mut seen_dot = false;
let mut seen_exp = false;
let mut index = 0usize;
if index < bytes.len() && (bytes[index] == b'+' || bytes[index] == b'-') {
index += 1;
}
if trimmed[index..].starts_with("Infinity") {
let sign = if bytes.first() == Some(&b'-') {
-1.0
} else {
1.0
};
return sign * f64::INFINITY;
}
while index < bytes.len() {
let b = bytes[index];
if b.is_ascii_digit() {
seen_digit = true;
end = index + 1;
} else if b == b'.' && !seen_dot && !seen_exp {
seen_dot = true;
} else if (b == b'e' || b == b'E') && seen_digit && !seen_exp {
let mut next = index + 1;
if next < bytes.len() && (bytes[next] == b'+' || bytes[next] == b'-') {
next += 1;
}
if next < bytes.len() && bytes[next].is_ascii_digit() {
seen_exp = true;
index = next;
end = index + 1;
continue;
} else {
break;
}
} else {
break;
}
index += 1;
}
if !seen_digit {
return f64::NAN;
}
trimmed[..end].parse::<f64>().unwrap_or(f64::NAN)
}
pub(super) fn js_number(value: Option<&Value>) -> f64 {
match value {
None => f64::NAN,
Some(Value::Null) => 0.0,
Some(Value::Bool(flag)) => {
if *flag {
1.0
} else {
0.0
}
}
Some(Value::Number(number)) => number.as_f64().unwrap_or(f64::NAN),
Some(Value::String(text)) => {
let trimmed = text.trim();
if trimmed.is_empty() {
0.0
} else if trimmed == "Infinity" || trimmed == "+Infinity" {
f64::INFINITY
} else if trimmed == "-Infinity" {
f64::NEG_INFINITY
} else {
trimmed.parse::<f64>().unwrap_or(f64::NAN)
}
}
Some(_) => f64::NAN,
}
}
pub(super) fn cv_from_raw(value: Option<&Value>) -> f64 {
match value {
None | Some(Value::Null) => f64::NAN,
other => js_number(other),
}
}
pub(super) fn js_parse_int(value: Option<&Value>) -> Option<i64> {
match value {
Some(Value::Number(number)) => {
let float = number.as_f64()?;
if float.is_finite() {
Some(float.trunc() as i64)
} else {
None
}
}
Some(Value::String(text)) => {
let trimmed = text.trim_start();
let bytes = trimmed.as_bytes();
let mut index = 0usize;
let mut sign = 1i64;
if index < bytes.len() && (bytes[index] == b'+' || bytes[index] == b'-') {
if bytes[index] == b'-' {
sign = -1;
}
index += 1;
}
let start = index;
while index < bytes.len() && bytes[index].is_ascii_digit() {
index += 1;
}
if index == start {
return None;
}
trimmed[start..index].parse::<i64>().ok().map(|v| sign * v)
}
_ => None,
}
}
pub(super) fn truthy(value: Option<&Value>) -> bool {
match value {
None | Some(Value::Null) => false,
Some(Value::Bool(flag)) => *flag,
Some(Value::Number(number)) => {
let float = number.as_f64().unwrap_or(f64::NAN);
float != 0.0 && !float.is_nan()
}
Some(Value::String(text)) => !text.is_empty(),
Some(_) => true,
}
}
pub(super) fn fmt_number(x: f64) -> String {
if x.is_nan() {
return "NaN".to_string();
}
if x == 0.0 {
return "0".to_string();
}
format!("{}", x)
}
pub fn fmt_id(value: &Value) -> String {
match value {
Value::String(text) => text.clone(),
Value::Number(number) => number
.as_f64()
.map(fmt_number)
.unwrap_or_else(|| number.to_string()),
Value::Bool(flag) => flag.to_string(),
Value::Null => "null".to_string(),
other => other.to_string(),
}
}
pub(super) fn json_num(x: f64) -> Value {
serde_json::Number::from_f64(x)
.map(Value::Number)
.unwrap_or(Value::Null)
}
pub(super) fn normalize_angle(angle: f64) -> f64 {
((angle % 360.0) + 360.0) % 360.0
}
pub(super) fn shortest_angle_delta(target: f64, current: f64) -> f64 {
let delta = normalize_angle(target - current);
if delta > 180.0 {
delta - 360.0
} else {
delta
}
}
pub(super) fn relative_delta_ratio(a: f64, b: f64, floor: f64) -> f64 {
let denom = a.abs().max(b.abs()).max(floor);
(a - b).abs() / denom
}
pub(super) fn sig_bits(x: f64) -> u64 {
if x == 0.0 {
0u64
} else if x.is_nan() {
f64::NAN.to_bits()
} else {
x.to_bits()
}
}