use thiserror::Error;
const INVALID_PATTERN: u64 = 0x7ff0_0000_0000_0000;
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum NumberSerializationError {
#[error("Unserializable number")]
Unserializable,
}
pub(crate) fn number_to_json(ieee_f64: f64) -> Result<String, NumberSerializationError> {
let ieee_u64 = ieee_f64.to_bits();
if (ieee_u64 & INVALID_PATTERN) == INVALID_PATTERN {
return Err(NumberSerializationError::Unserializable);
}
if ieee_f64 == 0.0 {
return Ok("0".to_string());
}
let (sign, num) = if ieee_f64.is_sign_negative() {
("-", -ieee_f64)
} else {
("", ieee_f64)
};
let es6_formatted = if (1e-6..1e21).contains(&num) {
num.to_string()
} else {
let mut scientific = format!("{:e}", num);
if let Some(e_pos) = scientific.find('e') {
let next_char_pos = e_pos + 1;
if scientific
.chars()
.nth(next_char_pos)
.is_some_and(|c| c.is_ascii_digit())
{
scientific.insert(next_char_pos, '+');
}
}
scientific
};
Ok(format!("{}{}", sign, es6_formatted))
}
#[cfg(test)]
mod tests {
use super::*;
use std::f64;
#[test]
fn test_zero() {
assert_eq!(number_to_json(0.0).unwrap(), "0");
}
#[test]
fn test_negative_zero() {
assert_eq!(number_to_json(-0.0).unwrap(), "0");
}
#[test]
fn test_simple_integer() {
assert_eq!(number_to_json(25.0).unwrap(), "25");
}
#[test]
fn test_negative_integer() {
assert_eq!(number_to_json(-50.0).unwrap(), "-50");
}
#[test]
fn test_simple_float() {
assert_eq!(number_to_json(2.5).unwrap(), "2.5");
}
#[test]
fn test_negative_float() {
assert_eq!(number_to_json(-3.5).unwrap(), "-3.5");
}
#[test]
fn test_fixed_point_boundary_small() {
assert_eq!(number_to_json(1e-6).unwrap(), "0.000001");
}
#[test]
fn test_fixed_point_boundary_large() {
assert_eq!(number_to_json(1e21).unwrap(), "1e+21");
assert_eq!(
number_to_json(9.999999999999999e20).unwrap(),
"999999999999999900000"
);
}
#[test]
fn test_scientific_positive_exponent() {
assert_eq!(number_to_json(1.23e22).unwrap(), "1.23e+22");
}
#[test]
fn test_scientific_negative_exponent() {
assert_eq!(number_to_json(1.23e-7).unwrap(), "1.23e-7");
}
#[test]
fn test_scientific_no_plus_needed() {
let n = -1.23e-7;
assert_eq!(number_to_json(n).unwrap(), "-1.23e-7");
}
#[test]
fn test_scientific_plus_insertion() {
let n = 1e9;
assert_eq!(number_to_json(n).unwrap(), "1000000000");
}
#[test]
fn test_scientific_plus_insertion_negative_number() {
let n = -1e12;
assert_eq!(number_to_json(n).unwrap(), "-1000000000000");
}
#[test]
fn test_invalid_numbers() {
assert!(number_to_json(f64::NAN).is_err());
assert!(number_to_json(f64::INFINITY).is_err());
assert!(number_to_json(f64::NEG_INFINITY).is_err());
}
#[test]
fn test_error_message() {
#[allow(unreachable_patterns)]
match number_to_json(f64::NAN).unwrap_err() {
NumberSerializationError::Unserializable => {}
_ => unreachable!(),
}
}
}