use rquickjs::{
function::{Opt, This},
Ctx, Exception, Result, Value,
};
use std::result::Result as StdResult;
const DIGITS: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz";
const BUF_SIZE: usize = 80;
const BIN_MAX_DIGITS: usize = 64;
const OCT_MAX_DIGITS: usize = 21;
#[inline(always)]
pub fn to_dec(number: i64) -> String {
itoa::Buffer::new().format(number).into()
}
#[inline(always)]
pub fn to_base_less_than_10(buf: &mut [u8], num: i64, base: i64) -> String {
let max = buf.len();
if num == 0 {
return "0".into();
}
let mut index = max;
let mut n = num;
let mut string = String::with_capacity(max + 1);
if n < 0 {
n = !n + 1;
string.push('-');
}
while n > 0 {
index -= 1;
buf[index] = (n % base) as u8 + b'0';
n /= base;
}
string.push_str(unsafe { std::str::from_utf8_unchecked(&buf[index..max]) });
string
}
pub fn i64_to_base_n(number: i64, radix: u8) -> String {
match radix {
10 => {
return to_dec(number);
}
2 => {
let mut buf = [0u8; BIN_MAX_DIGITS];
return to_base_less_than_10(&mut buf, number, 2);
}
8 => {
let mut buf = [0u8; OCT_MAX_DIGITS];
return to_base_less_than_10(&mut buf, number, 8);
}
_ => {}
}
let mut abs_number = number;
let mut buf = [0u8; BUF_SIZE];
let mut string = String::with_capacity(BUF_SIZE);
if number < 0 {
abs_number = -number;
string.push('-');
}
let index = internal_i64_to_base_n(&mut buf, abs_number, radix);
string.push_str(unsafe { std::str::from_utf8_unchecked(&buf[index..BUF_SIZE]) });
string
}
#[inline(always)]
fn internal_i64_to_base_n(buf: &mut [u8], number: i64, radix: u8) -> usize {
let mut n = number;
let mut index = BUF_SIZE;
while n > 0 {
index -= 1;
let digit = n % radix as i64;
buf[index] = DIGITS[digit as usize];
n /= radix as i64;
}
index
}
#[inline(always)]
fn next_up(num: f64) -> f64 {
const TINY_BITS: u64 = 0x1;
const CLEAR_SIGN_MASK: u64 = 0x7fff_ffff_ffff_ffff;
let bits = num.to_bits();
if num.is_nan() || bits == f64::INFINITY.to_bits() {
return num;
}
let abs = bits & CLEAR_SIGN_MASK;
let next_bits = if abs == 0 {
TINY_BITS
} else if bits == abs {
bits + 1
} else {
bits - 1
};
f64::from_bits(next_bits)
}
#[inline(always)]
fn fractional_to_base(buf: &mut [u8], mut index: usize, mut number: f64, radix: u8) -> usize {
let mut is_odd = number <= 0x1fffffffffffffi64 as f64 && (number as i64) & 1 != 0;
let mut digit;
let next_number = next_up(number);
let mut delta_next_double = next_number - number;
loop {
let ntmp = number * radix as f64;
let rtmp = delta_next_double * radix as f64;
digit = ntmp as usize;
let ritmp = rtmp as usize;
if digit & 1 != 0 {
is_odd = !is_odd;
}
number = ntmp - digit as f64;
delta_next_double = rtmp - ritmp as f64;
if number > 0.5f64 || number == 0.5f64 && if radix & 1 > 0 { is_odd } else { digit & 1 > 0 }
{
if number + delta_next_double > 1.0 {
break;
}
} else if number < delta_next_double * 2.0 {
break;
}
buf[index] = DIGITS[digit];
index += 1;
}
index
}
#[inline(always)]
fn f64_to_base_n(number: f64, radix: u8) -> String {
let mut abs_num = number;
let mut string = String::with_capacity(BUF_SIZE);
let mut buf = [0u8; BUF_SIZE];
if number < 0.0 {
abs_num = -number;
string.push('-');
}
let integer_part = abs_num.trunc();
let fractional_part = abs_num - integer_part;
let integer_part = abs_num as i64;
let mut index = internal_i64_to_base_n(&mut buf, integer_part, radix);
string.push_str(unsafe { std::str::from_utf8_unchecked(&buf[index..BUF_SIZE]) });
index = BUF_SIZE - index;
let dot_index = index;
index = fractional_to_base(&mut buf, index + 1, fractional_part, radix);
if index - 1 > dot_index {
buf[dot_index] = b'.';
}
string.push_str(unsafe { std::str::from_utf8_unchecked(&buf[..index]) });
string
}
pub fn float_to_string(buffer: &mut ryu::Buffer, float: f64) -> &str {
let str = match float_to_str(buffer, float) {
Ok(value) => value,
Err(value) => return value,
};
let len = str.len();
if unsafe { str.get_unchecked(len - 2..) } == ".0" {
return unsafe { std::str::from_utf8_unchecked(&str.as_bytes()[..len - 2]) };
}
str
}
#[inline(always)]
pub fn float_to_str(buf: &mut ryu::Buffer, float: f64) -> StdResult<&str, &str> {
const EXP_MASK: u64 = 0x7ff0000000000000;
let bits = float.to_bits();
if bits & EXP_MASK == EXP_MASK {
return Err(get_nonfinite(bits));
}
let str = buf.format_finite(float);
Ok(str)
}
#[inline(always)]
#[cold]
fn get_nonfinite<'a>(bits: u64) -> &'a str {
const MANTISSA_MASK: u64 = 0x000fffffffffffff;
const SIGN_MASK: u64 = 0x8000000000000000;
if bits & MANTISSA_MASK != 0 {
"NaN"
} else if bits & SIGN_MASK != 0 {
"-Infinity"
} else {
"Infinity"
}
}
#[inline(always)]
#[cold]
fn check_radix(ctx: &Ctx, radix: u8) -> Result<()> {
if !(2..=36).contains(&radix) {
return Err(Exception::throw_type(ctx, "radix must be between 2 and 36"));
}
Ok(())
}
pub fn number_to_string(ctx: Ctx, this: This<Value>, radix: Opt<u8>) -> Result<String> {
if let Some(int) = this.as_int() {
if let Some(radix) = radix.0 {
check_radix(&ctx, radix)?;
return Ok(i64_to_base_n(int as i64, radix));
}
let mut buffer = itoa::Buffer::new();
return Ok(buffer.format(int).into());
}
if let Some(float) = this.as_float() {
if let Some(radix) = radix.0 {
check_radix(&ctx, radix)?;
return Ok(f64_to_base_n(float, radix));
}
let mut buffer = ryu::Buffer::new();
return Ok(float_to_string(&mut buffer, float).into());
}
Ok("".into())
}
#[cfg(test)]
mod test {
use rand::{thread_rng, Rng};
use crate::number::{float_to_string, i64_to_base_n};
#[test]
fn test_base_conversions() {
let mut rng = thread_rng();
for _ in 0..1_000_000 {
let num: i64 = rng.gen_range(i64::MIN + 1..i64::MAX - 1);
let minus_str = if num < 0 { "-" } else { "" };
let expected_bin = format!("{}{:b}", minus_str, num.abs());
let actual_bin = i64_to_base_n(num, 2);
assert_eq!(expected_bin, actual_bin);
let expected_octal = format!("{}{:o}", minus_str, num.abs());
let actual_octal = i64_to_base_n(num, 8);
assert_eq!(expected_octal, actual_octal);
let expected_hex = format!("{}{:x}", minus_str, num.abs());
let actual_hex = i64_to_base_n(num, 16);
assert_eq!(expected_hex, actual_hex);
}
let base_36 = i64_to_base_n(123456789, 36);
assert_eq!("21i3v9", base_36);
let base_36 = i64_to_base_n(-123456789, 36);
assert_eq!("-21i3v9", base_36);
let mut buf = ryu::Buffer::new();
let float = float_to_string(&mut buf, 123.456);
assert_eq!("123.456", float);
let float = float_to_string(&mut buf, 123.);
assert_eq!("123", float);
let float = float_to_string(&mut buf, 0.0);
assert_eq!("0", float);
}
}