use std::borrow::{Borrow, ToOwned};
use std::fmt;
use std::ops::Deref;
use std::str::FromStr;
#[cfg(feature = "serde")]
pub mod serde;
#[cfg(feature = "serde_json")]
pub mod serde_json;
#[cfg(feature = "smallnumberbuf")]
mod smallnumberbuf {
use super::*;
use smallvec::SmallVec;
pub type SmallNumberBuf<const LEN: usize = 8> = NumberBuf<SmallVec<[u8; LEN]>>;
unsafe impl<A: smallvec::Array<Item = u8>> crate::Buffer for SmallVec<A> {
fn from_vec(bytes: Vec<u8>) -> Self {
bytes.into()
}
fn from_bytes(bytes: &[u8]) -> Self {
bytes.into()
}
}
}
#[cfg(feature = "smallnumberbuf")]
pub use smallnumberbuf::*;
#[derive(Clone, Copy, Debug)]
pub struct InvalidNumber<T>(pub T);
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum Sign {
Negative,
Zero,
Positive,
}
impl Sign {
#[inline(always)]
pub fn is_zero(&self) -> bool {
matches!(self, Self::Zero)
}
#[inline(always)]
pub fn is_non_positive(&self) -> bool {
matches!(self, Self::Negative | Self::Zero)
}
#[inline(always)]
pub fn is_non_negative(&self) -> bool {
matches!(self, Self::Positive | Self::Zero)
}
#[inline(always)]
pub fn is_positive(&self) -> bool {
matches!(self, Self::Positive)
}
#[inline(always)]
pub fn is_negative(&self) -> bool {
matches!(self, Self::Negative)
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Number {
data: [u8],
}
impl Number {
pub fn new<B: AsRef<[u8]> + ?Sized>(data: &B) -> Result<&Number, InvalidNumber<&B>> {
let s = data.as_ref();
enum State {
Init,
FirstDigit,
Zero,
NonZero,
FractionalFirst,
FractionalRest,
ExponentSign,
ExponentFirst,
ExponentRest,
}
let mut state = State::Init;
for b in s {
match state {
State::Init => match *b {
b'-' => state = State::FirstDigit,
b'0' => state = State::Zero,
b'1'..=b'9' => state = State::NonZero,
_ => return Err(InvalidNumber(data)),
},
State::FirstDigit => match *b {
b'0' => state = State::Zero,
b'1'..=b'9' => state = State::NonZero,
_ => return Err(InvalidNumber(data)),
},
State::Zero => match *b {
b'.' => state = State::FractionalFirst,
b'e' | b'E' => state = State::ExponentSign,
_ => return Err(InvalidNumber(data)),
},
State::NonZero => match *b {
b'0'..=b'9' => state = State::NonZero,
b'.' => state = State::FractionalFirst,
b'e' | b'E' => state = State::ExponentSign,
_ => return Err(InvalidNumber(data)),
},
State::FractionalFirst => match *b {
b'0'..=b'9' => state = State::FractionalRest,
_ => return Err(InvalidNumber(data)),
},
State::FractionalRest => match *b {
b'0'..=b'9' => state = State::FractionalRest,
b'e' | b'E' => state = State::ExponentSign,
_ => return Err(InvalidNumber(data)),
},
State::ExponentSign => match *b {
b'+' | b'-' => state = State::ExponentFirst,
b'0'..=b'9' => state = State::ExponentRest,
_ => return Err(InvalidNumber(data)),
},
State::ExponentFirst => match *b {
b'0'..=b'9' => state = State::ExponentRest,
_ => return Err(InvalidNumber(data)),
},
State::ExponentRest => match *b {
b'0'..=b'9' => state = State::ExponentRest,
_ => return Err(InvalidNumber(data)),
},
}
}
if matches!(
state,
State::Zero | State::NonZero | State::FractionalRest | State::ExponentRest
) {
Ok(unsafe { Self::new_unchecked(s) })
} else {
Err(InvalidNumber(data))
}
}
#[inline(always)]
pub unsafe fn new_unchecked<B: AsRef<[u8]> + ?Sized>(data: &B) -> &Number {
std::mem::transmute(data.as_ref())
}
#[inline(always)]
pub fn as_str(&self) -> &str {
unsafe {
std::str::from_utf8_unchecked(&self.data)
}
}
#[inline(always)]
pub fn is_zero(&self) -> bool {
for b in &self.data {
match b {
b'-' | b'0' | b'.' => (),
b'e' | b'E' => break,
_ => return false,
}
}
true
}
pub fn sign(&self) -> Sign {
let mut non_negative = true;
for b in &self.data {
match b {
b'-' => non_negative = false,
b'0' | b'.' => (),
b'e' | b'E' => break,
_ => {
return if non_negative {
Sign::Positive
} else {
Sign::Negative
}
}
}
}
Sign::Zero
}
#[inline(always)]
pub fn is_non_positive(&self) -> bool {
self.sign().is_non_positive()
}
#[inline(always)]
pub fn is_non_negative(&self) -> bool {
self.sign().is_non_negative()
}
#[inline(always)]
pub fn is_positive(&self) -> bool {
self.sign().is_positive()
}
#[inline(always)]
pub fn is_negative(&self) -> bool {
self.sign().is_negative()
}
#[inline(always)]
pub fn has_decimal_point(&self) -> bool {
self.data.contains(&b'.')
}
#[inline(always)]
pub fn has_fraction(&self) -> bool {
self.has_decimal_point()
}
#[inline(always)]
pub fn has_exponent(&self) -> bool {
for b in &self.data {
if matches!(b, b'e' | b'E') {
return true;
}
}
false
}
#[inline(always)]
pub fn is_i32(&self) -> bool {
self.as_i32().is_some()
}
#[inline(always)]
pub fn is_i64(&self) -> bool {
self.as_i64().is_some()
}
#[inline(always)]
pub fn is_u32(&self) -> bool {
self.as_u32().is_some()
}
#[inline(always)]
pub fn is_u64(&self) -> bool {
self.as_u64().is_some()
}
#[inline(always)]
pub fn as_i32(&self) -> Option<i32> {
self.as_str().parse().ok()
}
#[inline(always)]
pub fn as_i64(&self) -> Option<i64> {
self.as_str().parse().ok()
}
#[inline(always)]
pub fn as_u32(&self) -> Option<u32> {
self.as_str().parse().ok()
}
#[inline(always)]
pub fn as_u64(&self) -> Option<u64> {
self.as_str().parse().ok()
}
#[inline(always)]
pub fn as_f32_lossy(&self) -> f32 {
lexical::parse_with_options::<_, _, { lexical::format::JSON }>(
self.as_bytes(),
&LOSSY_PARSE_FLOAT,
)
.unwrap()
}
#[inline(always)]
pub fn as_f64_lossy(&self) -> f64 {
lexical::parse_with_options::<_, _, { lexical::format::JSON }>(
self.as_bytes(),
&LOSSY_PARSE_FLOAT,
)
.unwrap()
}
#[cfg(feature = "canonical")]
pub fn canonical_with<'b>(&self, buffer: &'b mut ryu_js::Buffer) -> &'b Number {
unsafe { Number::new_unchecked(buffer.format_finite(self.as_f64_lossy())) }
}
#[cfg(feature = "canonical")]
pub fn canonical(&self) -> NumberBuf {
let mut buffer = ryu_js::Buffer::new();
self.canonical_with(&mut buffer).to_owned()
}
}
const LOSSY_PARSE_FLOAT: lexical::ParseFloatOptions = unsafe {
lexical::ParseFloatOptions::builder()
.lossy(true)
.build_unchecked()
};
impl Deref for Number {
type Target = str;
#[inline(always)]
fn deref(&self) -> &str {
self.as_str()
}
}
impl AsRef<str> for Number {
#[inline(always)]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl Borrow<str> for Number {
#[inline(always)]
fn borrow(&self) -> &str {
self.as_str()
}
}
impl AsRef<[u8]> for Number {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl Borrow<[u8]> for Number {
#[inline(always)]
fn borrow(&self) -> &[u8] {
self.as_bytes()
}
}
impl<'a> TryFrom<&'a str> for &'a Number {
type Error = InvalidNumber<&'a str>;
#[inline(always)]
fn try_from(s: &'a str) -> Result<&'a Number, InvalidNumber<&'a str>> {
Number::new(s)
}
}
impl ToOwned for Number {
type Owned = NumberBuf;
fn to_owned(&self) -> Self::Owned {
unsafe { NumberBuf::new_unchecked(self.as_bytes().to_owned()) }
}
}
impl fmt::Display for Number {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.as_str().fmt(f)
}
}
impl fmt::Debug for Number {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.as_str().fmt(f)
}
}
pub unsafe trait Buffer: AsRef<[u8]> {
fn from_bytes(bytes: &[u8]) -> Self;
fn from_vec(bytes: Vec<u8>) -> Self;
}
unsafe impl Buffer for Vec<u8> {
fn from_bytes(bytes: &[u8]) -> Self {
bytes.into()
}
fn from_vec(bytes: Vec<u8>) -> Self {
bytes
}
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NumberBuf<B = Vec<u8>> {
data: B,
}
impl<B> NumberBuf<B> {
#[inline(always)]
pub fn new(data: B) -> Result<Self, InvalidNumber<B>>
where
B: AsRef<[u8]>,
{
match Number::new(&data) {
Ok(_) => Ok(NumberBuf { data }),
Err(_) => Err(InvalidNumber(data)),
}
}
#[inline(always)]
pub unsafe fn new_unchecked(data: B) -> Self {
NumberBuf { data }
}
#[inline(always)]
pub fn from_number(n: &Number) -> Self
where
B: FromIterator<u8>,
{
unsafe { NumberBuf::new_unchecked(n.bytes().collect()) }
}
#[inline(always)]
pub fn buffer(&self) -> &B {
&self.data
}
#[inline(always)]
pub fn into_buffer(self) -> B {
self.data
}
}
impl NumberBuf<String> {
#[inline(always)]
pub fn into_string(self) -> String {
self.data
}
#[inline(always)]
pub fn into_bytes(self) -> Vec<u8> {
self.data.into_bytes()
}
}
impl<B: Buffer> NumberBuf<B> {
#[inline(always)]
pub fn as_number(&self) -> &Number {
unsafe { Number::new_unchecked(&self.data) }
}
}
impl<B: Buffer> FromStr for NumberBuf<B> {
type Err = InvalidNumber<B>;
#[inline(always)]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(B::from_bytes(s.as_bytes()))
}
}
impl<B: Buffer> Deref for NumberBuf<B> {
type Target = Number;
#[inline(always)]
fn deref(&self) -> &Number {
self.as_number()
}
}
impl<B: Buffer> AsRef<Number> for NumberBuf<B> {
#[inline(always)]
fn as_ref(&self) -> &Number {
self.as_number()
}
}
impl<B: Buffer> Borrow<Number> for NumberBuf<B> {
#[inline(always)]
fn borrow(&self) -> &Number {
self.as_number()
}
}
impl<B: Buffer> AsRef<str> for NumberBuf<B> {
#[inline(always)]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl<B: Buffer> Borrow<str> for NumberBuf<B> {
#[inline(always)]
fn borrow(&self) -> &str {
self.as_str()
}
}
impl<B: Buffer> AsRef<[u8]> for NumberBuf<B> {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl<B: Buffer> Borrow<[u8]> for NumberBuf<B> {
#[inline(always)]
fn borrow(&self) -> &[u8] {
self.as_bytes()
}
}
impl<B: Buffer> fmt::Display for NumberBuf<B> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.as_str().fmt(f)
}
}
impl<B: Buffer> fmt::Debug for NumberBuf<B> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.as_str().fmt(f)
}
}
macro_rules! impl_from_int {
($($ty:ty),*) => {
$(
impl<B: Buffer> From<$ty> for NumberBuf<B> {
#[inline(always)]
fn from(i: $ty) -> Self {
unsafe {
Self::new_unchecked(B::from_vec(lexical::to_string(i).into_bytes()))
}
}
}
)*
};
}
#[derive(Clone, Copy, Debug)]
pub enum TryFromFloatError {
Nan,
Infinite,
}
const WRITE_FLOAT: lexical::WriteFloatOptions = unsafe {
lexical::WriteFloatOptions::builder()
.trim_floats(true)
.exponent(b'e')
.build_unchecked()
};
macro_rules! impl_try_from_float {
($($ty:ty),*) => {
$(
impl<B: Buffer> TryFrom<$ty> for NumberBuf<B> {
type Error = TryFromFloatError;
#[inline(always)]
fn try_from(f: $ty) -> Result<Self, Self::Error> {
if f.is_finite() {
Ok(unsafe {
Self::new_unchecked(B::from_vec(lexical::to_string_with_options::<_, {lexical::format::JSON}>(f, &WRITE_FLOAT).into_bytes()))
})
} else if f.is_nan() {
Err(TryFromFloatError::Nan)
} else {
Err(TryFromFloatError::Infinite)
}
}
}
)*
};
}
impl_from_int!(u8, i8, u16, i16, u32, i32, u64, i64, usize, isize);
impl_try_from_float!(f32, f64);
#[cfg(test)]
mod tests {
use super::*;
macro_rules! positive_tests {
{ $($id:ident: $input:literal),* } => {
$(
#[test]
fn $id () {
assert!(Number::new($input).is_ok())
}
)*
};
}
macro_rules! negative_tests {
{ $($id:ident: $input:literal),* } => {
$(
#[test]
fn $id () {
assert!(Number::new($input).is_err())
}
)*
};
}
macro_rules! sign_tests {
{ $($id:ident: $input:literal => $sign:ident),* } => {
$(
#[test]
fn $id () {
assert_eq!(Number::new($input).unwrap().sign(), Sign::$sign)
}
)*
};
}
macro_rules! canonical_tests {
{ $($id:ident: $input:literal => $output:literal),* } => {
$(
#[cfg(feature="canonical")]
#[test]
fn $id () {
assert_eq!(Number::new($input).unwrap().canonical().as_number(), Number::new($output).unwrap())
}
)*
};
}
positive_tests! {
pos_01: "0",
pos_02: "-0",
pos_03: "123",
pos_04: "1.23",
pos_05: "-12.34",
pos_06: "12.34e+56",
pos_07: "12.34E-56",
pos_08: "0.0000"
}
negative_tests! {
neg_01: "",
neg_02: "00",
neg_03: "01",
neg_04: "-00",
neg_05: "-01",
neg_06: "0.000e+-1",
neg_07: "12.34E-56abc",
neg_08: "1.",
neg_09: "12.34e",
neg_10: "12.34e+",
neg_11: "12.34E-"
}
sign_tests! {
sign_zero_01: "0" => Zero,
sign_zero_02: "-0" => Zero,
sign_zero_03: "0.0" => Zero,
sign_zero_04: "0.0e12" => Zero,
sign_zero_05: "-0.0E-12" => Zero,
sign_zero_06: "-0.00000" => Zero
}
sign_tests! {
sign_pos_01: "1" => Positive,
sign_pos_02: "0.1" => Positive,
sign_pos_03: "0.01e23" => Positive,
sign_pos_04: "1.0E-23" => Positive,
sign_pos_05: "0.00001" => Positive
}
sign_tests! {
sign_neg_01: "-1" => Negative,
sign_neg_02: "-0.1" => Negative,
sign_neg_03: "-0.01e23" => Negative,
sign_neg_04: "-1.0E-23" => Negative,
sign_neg_05: "-0.00001" => Negative
}
canonical_tests! {
canonical_01: "-0.0000" => "0",
canonical_02: "0.00000000028" => "2.8e-10"
}
}