Skip to main content

jstrict/number/
mod.rs

1//! JSON number parsing and storage.
2//!
3//! Provides two types: the unsized [`Number`] type acting like `str`, and the
4//! [`NumberBuf<B>`] type owning the data inside the `B` type (by default
5//! `Vec<u8>`).
6use core::ptr;
7use lexical::format::JSON;
8use smallvec::SmallVec;
9use std::borrow::{Borrow, ToOwned};
10use std::error::Error as StdError;
11use std::fmt;
12use std::ops::Deref;
13use std::str;
14use std::str::FromStr;
15
16/// `serde` support for [`Number`] and [`NumberBuf`].
17#[cfg(feature = "serde")]
18pub mod serde;
19
20/// Conversion between [`NumberBuf`] and `serde_json::Number`.
21#[cfg(feature = "serde_json")]
22pub mod serde_json;
23
24/// Conversion between [`NumberBuf`] and `sonic_rs::Number`.
25#[cfg(feature = "sonic-rs")]
26pub mod sonic_rs;
27
28/// JSON number buffer based on [`SmallVec`].
29pub type SmallNumberBuf<const LEN: usize = 8> = NumberBuf<SmallVec<[u8; LEN]>>;
30
31unsafe impl<A: smallvec::Array<Item = u8>> Buffer for SmallVec<A> {
32	fn from_vec(bytes: Vec<u8>) -> Self {
33		bytes.into()
34	}
35
36	fn from_bytes(bytes: &[u8]) -> Self {
37		bytes.into()
38	}
39}
40
41/// Invalid number error.
42///
43/// The inner value is the data failed to be parsed.
44#[derive(Clone, Copy, Debug)]
45pub struct InvalidNumber<T>(pub T);
46
47impl<T: fmt::Display> fmt::Display for InvalidNumber<T> {
48	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49		write!(f, "invalid JSON number: {}", self.0)
50	}
51}
52
53impl<T: fmt::Display + fmt::Debug> StdError for InvalidNumber<T> {}
54
55/// Number sign.
56#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
57pub enum Sign {
58	/// Strictly negative.
59	Negative,
60
61	/// Zero, whatever its lexical form (`0`, `-0`, `0.00e12`, …).
62	Zero,
63
64	/// Strictly positive.
65	Positive,
66}
67
68impl Sign {
69	/// Checks if the number is zero.
70	#[inline(always)]
71	#[must_use]
72	pub const fn is_zero(&self) -> bool {
73		matches!(self, Self::Zero)
74	}
75
76	/// Checks if the number is non positive (negative or zero).
77	#[inline(always)]
78	#[must_use]
79	pub const fn is_non_positive(&self) -> bool {
80		matches!(self, Self::Negative | Self::Zero)
81	}
82
83	/// Checks if the number is non negative (positive or zero).
84	#[inline(always)]
85	#[must_use]
86	pub const fn is_non_negative(&self) -> bool {
87		matches!(self, Self::Positive | Self::Zero)
88	}
89
90	/// Checks if the number is strictly positive (non zero nor negative).
91	#[inline(always)]
92	#[must_use]
93	pub const fn is_positive(&self) -> bool {
94		matches!(self, Self::Positive)
95	}
96
97	/// Checks if the number is strictly negative (non zero nor positive).
98	#[inline(always)]
99	#[must_use]
100	pub const fn is_negative(&self) -> bool {
101		matches!(self, Self::Negative)
102	}
103}
104
105/// Lexical JSON number.
106///
107/// This hold the lexical representation of a JSON number.
108/// All the comparison operations are done on this *lexical* representation,
109/// meaning that `1` is actually greater than `0.1e+80` for instance.
110#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
111#[repr(transparent)]
112pub struct Number {
113	data: [u8],
114}
115
116impl Number {
117	/// Creates a new number by parsing the given input `data`.
118	/// # Errors
119	///
120	/// Returns [`InvalidNumber`] if `data` is not a valid JSON number.
121	pub fn new<B: AsRef<[u8]> + ?Sized>(data: &B) -> Result<&Number, InvalidNumber<&B>> {
122		enum State {
123			Init,
124			FirstDigit,
125			Zero,
126			NonZero,
127			FractionalFirst,
128			FractionalRest,
129			ExponentSign,
130			ExponentFirst,
131			ExponentRest,
132		}
133
134		let s = data.as_ref();
135		let mut state = State::Init;
136
137		for b in s {
138			match state {
139				State::Init => match *b {
140					b'-' => state = State::FirstDigit,
141					b'0' => state = State::Zero,
142					b'1'..=b'9' => state = State::NonZero,
143					_ => return Err(InvalidNumber(data)),
144				},
145				State::FirstDigit => match *b {
146					b'0' => state = State::Zero,
147					b'1'..=b'9' => state = State::NonZero,
148					_ => return Err(InvalidNumber(data)),
149				},
150				State::Zero => match *b {
151					b'.' => state = State::FractionalFirst,
152					b'e' | b'E' => state = State::ExponentSign,
153					_ => return Err(InvalidNumber(data)),
154				},
155				State::NonZero => match *b {
156					b'0'..=b'9' => state = State::NonZero,
157					b'.' => state = State::FractionalFirst,
158					b'e' | b'E' => state = State::ExponentSign,
159					_ => return Err(InvalidNumber(data)),
160				},
161				State::FractionalFirst => match *b {
162					b'0'..=b'9' => state = State::FractionalRest,
163					_ => return Err(InvalidNumber(data)),
164				},
165				State::FractionalRest => match *b {
166					b'0'..=b'9' => state = State::FractionalRest,
167					b'e' | b'E' => state = State::ExponentSign,
168					_ => return Err(InvalidNumber(data)),
169				},
170				State::ExponentSign => match *b {
171					b'+' | b'-' => state = State::ExponentFirst,
172					b'0'..=b'9' => state = State::ExponentRest,
173					_ => return Err(InvalidNumber(data)),
174				},
175				State::ExponentFirst | State::ExponentRest => match *b {
176					b'0'..=b'9' => state = State::ExponentRest,
177					_ => return Err(InvalidNumber(data)),
178				},
179			}
180		}
181
182		if matches!(
183			state,
184			State::Zero | State::NonZero | State::FractionalRest | State::ExponentRest
185		) {
186			Ok(unsafe { Self::new_unchecked(s) })
187		} else {
188			Err(InvalidNumber(data))
189		}
190	}
191
192	/// Creates a new number without parsing the given input `data`.
193	///
194	/// ## Safety
195	///
196	/// The `data` input **must** be a valid JSON number.
197	#[inline(always)]
198	pub unsafe fn new_unchecked<B: AsRef<[u8]> + ?Sized>(data: &B) -> &Number {
199		unsafe { &*(ptr::from_ref(data.as_ref()) as *const Number) }
200	}
201
202	/// Returns the lexical representation of this number.
203	#[inline(always)]
204	#[must_use]
205	pub const fn as_str(&self) -> &str {
206		unsafe {
207			// safe because `self.data` is always a valid UTF-8 sequence.
208			str::from_utf8_unchecked(&self.data)
209		}
210	}
211
212	/// Strips insignificant trailing zeros from the fraction part.
213	///
214	/// `1.10000` becomes `1.1`, `1.0` becomes `1`. The exponent part, if any,
215	/// is left untouched.
216	#[must_use]
217	pub fn trimmed(&self) -> &Self {
218		let mut end = 1;
219		let mut i = 1;
220		let mut fractional_part = false;
221		while i < self.data.len() {
222			match self.data[i] {
223				b'0' if fractional_part => (),
224				b'.' => fractional_part = true,
225				_ => end = i + 1,
226			}
227
228			i += 1;
229		}
230
231		unsafe { Self::new_unchecked(&self.data[0..end]) }
232	}
233
234	/// Checks if the number is equal to zero (`0`).
235	///
236	/// This include every lexical representation where
237	/// the decimal and fraction part are composed of only
238	/// `0`, maybe preceded with `-`, and an arbitrary exponent part.
239	#[inline(always)]
240	#[must_use]
241	pub fn is_zero(&self) -> bool {
242		for b in &self.data {
243			match b {
244				b'-' | b'0' | b'.' => (),
245				b'e' | b'E' => break,
246				_ => return false,
247			}
248		}
249
250		true
251	}
252
253	/// Returns the sign of the number.
254	#[must_use]
255	pub fn sign(&self) -> Sign {
256		let mut non_negative = true;
257
258		for b in &self.data {
259			match b {
260				b'-' => non_negative = false,
261				b'0' | b'.' => (),
262				b'e' | b'E' => break,
263				_ => {
264					return if non_negative {
265						Sign::Positive
266					} else {
267						Sign::Negative
268					};
269				}
270			}
271		}
272
273		Sign::Zero
274	}
275
276	/// Checks if the number is non positive (negative or zero).
277	#[inline(always)]
278	#[must_use]
279	pub fn is_non_positive(&self) -> bool {
280		self.sign().is_non_positive()
281	}
282
283	/// Checks if the number is non negative (positive or zero).
284	#[inline(always)]
285	#[must_use]
286	pub fn is_non_negative(&self) -> bool {
287		self.sign().is_non_negative()
288	}
289
290	/// Checks if the number is strictly positive (non zero nor negative).
291	#[inline(always)]
292	#[must_use]
293	pub fn is_positive(&self) -> bool {
294		self.sign().is_positive()
295	}
296
297	/// Checks if the number is strictly negative (non zero nor positive).
298	#[inline(always)]
299	#[must_use]
300	pub fn is_negative(&self) -> bool {
301		self.sign().is_negative()
302	}
303
304	/// Checks if the number has a decimal point.
305	#[inline(always)]
306	#[must_use]
307	pub fn has_decimal_point(&self) -> bool {
308		self.data.contains(&b'.')
309	}
310
311	/// Checks if the number has a fraction part.
312	///
313	/// This is an alias for [`has_decimal_point`](Self::has_decimal_point).
314	#[inline(always)]
315	#[must_use]
316	pub fn has_fraction(&self) -> bool {
317		self.has_decimal_point()
318	}
319
320	/// Checks if the number has an exponent part.
321	#[inline(always)]
322	#[must_use]
323	pub fn has_exponent(&self) -> bool {
324		for b in &self.data {
325			if matches!(b, b'e' | b'E') {
326				return true;
327			}
328		}
329
330		false
331	}
332
333	/// Checks if this number is exactly representable as an `i32`.
334	#[inline(always)]
335	#[must_use]
336	pub fn is_i32(&self) -> bool {
337		self.as_i32().is_some()
338	}
339
340	/// Checks if this number is exactly representable as an `i64`.
341	#[inline(always)]
342	#[must_use]
343	pub fn is_i64(&self) -> bool {
344		self.as_i64().is_some()
345	}
346
347	/// Checks if this number is exactly representable as a `u32`.
348	#[inline(always)]
349	#[must_use]
350	pub fn is_u32(&self) -> bool {
351		self.as_u32().is_some()
352	}
353
354	/// Checks if this number is exactly representable as a `u64`.
355	#[inline(always)]
356	#[must_use]
357	pub fn is_u64(&self) -> bool {
358		self.as_u64().is_some()
359	}
360
361	/// Returns this number as an `i32`, or `None` if it is not an integer in
362	/// range.
363	#[inline(always)]
364	#[must_use]
365	pub fn as_i32(&self) -> Option<i32> {
366		self.as_str().parse().ok()
367	}
368
369	/// Returns this number as an `i64`, or `None` if it is not an integer in
370	/// range.
371	#[inline(always)]
372	#[must_use]
373	pub fn as_i64(&self) -> Option<i64> {
374		self.as_str().parse().ok()
375	}
376
377	/// Returns this number as a `u32`, or `None` if it is not an integer in
378	/// range.
379	#[inline(always)]
380	#[must_use]
381	pub fn as_u32(&self) -> Option<u32> {
382		self.as_str().parse().ok()
383	}
384
385	/// Returns this number as a `u64`, or `None` if it is not an integer in
386	/// range.
387	#[inline(always)]
388	#[must_use]
389	pub fn as_u64(&self) -> Option<u64> {
390		self.as_str().parse().ok()
391	}
392
393	/// Returns this number as an `f32`, rounding if necessary.
394	#[inline(always)]
395	#[must_use]
396	pub fn as_f32_lossy(&self) -> f32 {
397		lexical::parse_with_options::<_, _, { JSON }>(self.as_bytes(), &LOSSY_PARSE_FLOAT)
398			.unwrap_or(f32::NAN)
399	}
400
401	/// Returns the number as a `f32` only if the operation does not induce
402	/// imprecisions/approximations.
403	///
404	/// This operation is expensive as it requires allocating a new number
405	/// buffer to check the decimal representation of the generated `f32`.
406	#[inline(always)]
407	#[must_use]
408	pub fn as_f32_lossless(&self) -> Option<f32> {
409		let f = self.as_f32_lossy();
410		let Ok(n) = <NumberBuf as TryFrom<f32>>::try_from(f) else {
411			return None;
412		};
413		if n.as_number() == self.trimmed() {
414			Some(f)
415		} else {
416			None
417		}
418	}
419
420	/// Returns this number as an `f64`, rounding if necessary.
421	#[inline(always)]
422	#[must_use]
423	pub fn as_f64_lossy(&self) -> f64 {
424		lexical::parse_with_options::<_, _, { JSON }>(self.as_bytes(), &LOSSY_PARSE_FLOAT)
425			.unwrap_or(f64::NAN)
426	}
427
428	/// Returns the number as a `f64` only if the operation does not induce
429	/// imprecisions/approximations.
430	///
431	/// This operation is expensive as it requires allocating a new number
432	/// buffer to check the decimal representation of the generated `f64`.
433	#[inline(always)]
434	#[must_use]
435	pub fn as_f64_lossless(&self) -> Option<f64> {
436		let f = self.as_f64_lossy();
437		let Ok(n) = <NumberBuf as TryFrom<f64>>::try_from(f) else {
438			return None;
439		};
440		if n.as_number() == self { Some(f) } else { None }
441	}
442
443	/// Returns the canonical representation of this number according to
444	/// [RFC8785](https://www.rfc-editor.org/rfc/rfc8785#name-serialization-of-numbers).
445	#[cfg(feature = "canonicalize")]
446	pub fn canonical_with<'b>(&self, buffer: &'b mut ryu_js::Buffer) -> &'b Number {
447		unsafe { Number::new_unchecked(buffer.format_finite(self.as_f64_lossy())) }
448	}
449
450	/// Returns the canonical representation of this number according to
451	/// [RFC8785](https://www.rfc-editor.org/rfc/rfc8785#name-serialization-of-numbers).
452	#[cfg(feature = "canonicalize")]
453	#[must_use]
454	pub fn canonical(&self) -> NumberBuf {
455		let mut buffer = ryu_js::Buffer::new();
456		self.canonical_with(&mut buffer).to_owned()
457	}
458}
459
460const LOSSY_PARSE_FLOAT: lexical::ParseFloatOptions = lexical::ParseFloatOptions::builder()
461	.lossy(true)
462	.build_unchecked();
463
464impl Deref for Number {
465	type Target = str;
466
467	#[inline(always)]
468	fn deref(&self) -> &str {
469		self.as_str()
470	}
471}
472
473impl AsRef<str> for Number {
474	#[inline(always)]
475	fn as_ref(&self) -> &str {
476		self.as_str()
477	}
478}
479
480impl Borrow<str> for Number {
481	#[inline(always)]
482	fn borrow(&self) -> &str {
483		self.as_str()
484	}
485}
486
487impl AsRef<[u8]> for Number {
488	#[inline(always)]
489	fn as_ref(&self) -> &[u8] {
490		self.as_bytes()
491	}
492}
493
494impl<'a> TryFrom<&'a str> for &'a Number {
495	type Error = InvalidNumber<&'a str>;
496
497	#[inline(always)]
498	fn try_from(s: &'a str) -> Result<&'a Number, InvalidNumber<&'a str>> {
499		Number::new(s)
500	}
501}
502
503impl ToOwned for Number {
504	type Owned = NumberBuf;
505
506	fn to_owned(&self) -> Self::Owned {
507		unsafe { NumberBuf::new_unchecked(self.as_bytes().to_owned()) }
508	}
509}
510
511impl fmt::Display for Number {
512	#[inline(always)]
513	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
514		self.as_str().fmt(f)
515	}
516}
517
518impl fmt::Debug for Number {
519	#[inline(always)]
520	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
521		self.as_str().fmt(f)
522	}
523}
524
525/// Buffer type.
526///
527/// # Safety
528///
529/// The `AsRef<[u8]>` implementation *must* return the bytes provided using
530/// the `from_bytes` and `from_vec` constructor functions.
531pub unsafe trait Buffer: AsRef<[u8]> {
532	/// Builds a buffer holding exactly `bytes`.
533	fn from_bytes(bytes: &[u8]) -> Self;
534
535	/// Builds a buffer holding exactly `bytes`, reusing the allocation where
536	/// possible.
537	fn from_vec(bytes: Vec<u8>) -> Self;
538}
539
540unsafe impl Buffer for Vec<u8> {
541	fn from_bytes(bytes: &[u8]) -> Self {
542		bytes.into()
543	}
544
545	fn from_vec(bytes: Vec<u8>) -> Self {
546		bytes
547	}
548}
549
550/// JSON number buffer.
551#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
552pub struct NumberBuf<B = Vec<u8>> {
553	data: B,
554}
555
556impl<B> NumberBuf<B> {
557	/// Creates a new number buffer by parsing the given input `data` buffer.
558	#[inline(always)]
559	/// # Errors
560	///
561	/// Returns [`InvalidNumber`] if `data` is not a valid JSON number.
562	pub fn new(data: B) -> Result<Self, InvalidNumber<B>>
563	where
564		B: AsRef<[u8]>,
565	{
566		match Number::new(&data) {
567			Ok(_) => Ok(NumberBuf { data }),
568			Err(_) => Err(InvalidNumber(data)),
569		}
570	}
571
572	/// Creates a new number buffer from the given input `data` buffer.
573	///
574	/// ## Safety
575	///
576	/// The input `data` **must** hold a valid JSON number string.
577	#[inline(always)]
578	pub const unsafe fn new_unchecked(data: B) -> Self {
579		NumberBuf { data }
580	}
581
582	/// Creates a number buffer from the given `number`.
583	#[inline(always)]
584	#[must_use]
585	pub fn from_number(n: &Number) -> Self
586	where
587		B: FromIterator<u8>,
588	{
589		unsafe { NumberBuf::new_unchecked(n.bytes().collect()) }
590	}
591
592	/// Returns a reference to the underlying buffer.
593	#[inline(always)]
594	pub const fn buffer(&self) -> &B {
595		&self.data
596	}
597
598	/// Consumes this number, returning the underlying buffer.
599	#[inline(always)]
600	pub fn into_buffer(self) -> B {
601		self.data
602	}
603}
604
605impl NumberBuf<String> {
606	/// Consumes this number, returning its lexical representation.
607	#[inline(always)]
608	#[must_use]
609	pub fn into_string(self) -> String {
610		self.data
611	}
612
613	/// Consumes this number, returning the bytes of its lexical
614	/// representation.
615	#[inline(always)]
616	#[must_use]
617	pub fn into_bytes(self) -> Vec<u8> {
618		self.data.into_bytes()
619	}
620}
621
622impl<B: Buffer> NumberBuf<B> {
623	/// Borrows this number buffer as an unsized [`Number`].
624	#[inline(always)]
625	pub fn as_number(&self) -> &Number {
626		unsafe { Number::new_unchecked(&self.data) }
627	}
628}
629
630impl<B: Buffer> FromStr for NumberBuf<B> {
631	type Err = InvalidNumber<B>;
632
633	#[inline(always)]
634	fn from_str(s: &str) -> Result<Self, Self::Err> {
635		Self::new(B::from_bytes(s.as_bytes()))
636	}
637}
638
639impl<B: Buffer> Deref for NumberBuf<B> {
640	type Target = Number;
641
642	#[inline(always)]
643	fn deref(&self) -> &Number {
644		self.as_number()
645	}
646}
647
648impl<B: Buffer> AsRef<Number> for NumberBuf<B> {
649	#[inline(always)]
650	fn as_ref(&self) -> &Number {
651		self.as_number()
652	}
653}
654
655impl<B: Buffer> Borrow<Number> for NumberBuf<B> {
656	#[inline(always)]
657	fn borrow(&self) -> &Number {
658		self.as_number()
659	}
660}
661
662impl<B: Buffer> AsRef<str> for NumberBuf<B> {
663	#[inline(always)]
664	fn as_ref(&self) -> &str {
665		self.as_str()
666	}
667}
668
669impl<B: Buffer> Borrow<str> for NumberBuf<B> {
670	#[inline(always)]
671	fn borrow(&self) -> &str {
672		self.as_str()
673	}
674}
675
676impl<B: Buffer> AsRef<[u8]> for NumberBuf<B> {
677	#[inline(always)]
678	fn as_ref(&self) -> &[u8] {
679		self.as_bytes()
680	}
681}
682
683impl<B: Buffer> Borrow<[u8]> for NumberBuf<B> {
684	#[inline(always)]
685	fn borrow(&self) -> &[u8] {
686		self.as_bytes()
687	}
688}
689
690impl<B: Buffer> fmt::Display for NumberBuf<B> {
691	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
692		self.as_str().fmt(f)
693	}
694}
695
696impl<B: Buffer> fmt::Debug for NumberBuf<B> {
697	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
698		self.as_str().fmt(f)
699	}
700}
701
702macro_rules! impl_from_int {
703	($($ty:ty),*) => {
704		$(
705			impl<B: Buffer> From<$ty> for NumberBuf<B> {
706				#[inline(always)]
707				fn from(i: $ty) -> Self {
708					unsafe {
709						Self::new_unchecked(B::from_vec(lexical::to_string(i).into_bytes()))
710					}
711				}
712			}
713		)*
714	};
715}
716
717/// Float conversion error.
718#[derive(Clone, Copy, Debug)]
719pub enum TryFromFloatError {
720	/// The float was Nan, which is not a JSON number.
721	Nan,
722
723	/// The float was not finite, and hence not a JSON number.
724	Infinite,
725}
726
727const WRITE_FLOAT: lexical::WriteFloatOptions = lexical::WriteFloatOptions::builder()
728	.trim_floats(true)
729	.exponent(b'e')
730	.build_unchecked();
731
732macro_rules! impl_try_from_float {
733	($($ty:ty),*) => {
734		$(
735			impl<B: Buffer> TryFrom<$ty> for NumberBuf<B> {
736				type Error = TryFromFloatError;
737
738				#[inline(always)]
739				fn try_from(f: $ty) -> Result<Self, Self::Error> {
740					if f.is_finite() {
741						Ok(unsafe {
742							Self::new_unchecked(B::from_vec(lexical::to_string_with_options::<_, {lexical::format::JSON}>(f, &WRITE_FLOAT).into_bytes()))
743						})
744					} else if f.is_nan() {
745						Err(TryFromFloatError::Nan)
746					} else {
747						Err(TryFromFloatError::Infinite)
748					}
749				}
750			}
751		)*
752	};
753}
754
755impl_from_int!(u8, i8, u16, i16, u32, i32, u64, i64, usize, isize);
756impl_try_from_float!(f32, f64);
757
758#[cfg(test)]
759mod tests {
760	use super::*;
761
762	fn trimming_test(a: &str, b: &str) {
763		let a = Number::new(a).unwrap();
764		let b = Number::new(b).unwrap();
765		assert_eq!(a.trimmed(), b);
766	}
767
768	#[test]
769	fn trimming() {
770		trimming_test("0", "0");
771		trimming_test("0.0", "0");
772		trimming_test("1.0", "1");
773		trimming_test("1.0", "1");
774		trimming_test("1.1", "1.1");
775		trimming_test("1.10000", "1.1");
776		trimming_test("100.0", "100");
777		trimming_test("100.1000", "100.1");
778	}
779
780	macro_rules! positive_tests {
781		{ $($id:ident: $input:literal),* } => {
782			$(
783				#[test]
784				fn $id () {
785					assert!(Number::new($input).is_ok())
786				}
787			)*
788		};
789	}
790
791	macro_rules! negative_tests {
792		{ $($id:ident: $input:literal),* } => {
793			$(
794				#[test]
795				fn $id () {
796					assert!(Number::new($input).is_err())
797				}
798			)*
799		};
800	}
801
802	macro_rules! sign_tests {
803		{ $($id:ident: $input:literal => $sign:ident),* } => {
804			$(
805				#[test]
806				fn $id () {
807					assert_eq!(Number::new($input).unwrap().sign(), Sign::$sign)
808				}
809			)*
810		};
811	}
812
813	macro_rules! canonical_tests {
814		{ $($id:ident: $input:literal => $output:literal),* } => {
815			$(
816				#[cfg(feature="canonicalize")]
817				#[test]
818				fn $id () {
819					assert_eq!(Number::new($input).unwrap().canonical().as_number(), Number::new($output).unwrap())
820				}
821			)*
822		};
823	}
824
825	positive_tests! {
826		pos_01: "0",
827		pos_02: "-0",
828		pos_03: "123",
829		pos_04: "1.23",
830		pos_05: "-12.34",
831		pos_06: "12.34e+56",
832		pos_07: "12.34E-56",
833		pos_08: "0.0000"
834	}
835
836	negative_tests! {
837		neg_01: "",
838		neg_02: "00",
839		neg_03: "01",
840		neg_04: "-00",
841		neg_05: "-01",
842		neg_06: "0.000e+-1",
843		neg_07: "12.34E-56abc",
844		neg_08: "1.",
845		neg_09: "12.34e",
846		neg_10: "12.34e+",
847		neg_11: "12.34E-"
848	}
849
850	sign_tests! {
851		sign_zero_01: "0" => Zero,
852		sign_zero_02: "-0" => Zero,
853		sign_zero_03: "0.0" => Zero,
854		sign_zero_04: "0.0e12" => Zero,
855		sign_zero_05: "-0.0E-12" => Zero,
856		sign_zero_06: "-0.00000" => Zero
857	}
858
859	sign_tests! {
860		sign_pos_01: "1" => Positive,
861		sign_pos_02: "0.1" => Positive,
862		sign_pos_03: "0.01e23" => Positive,
863		sign_pos_04: "1.0E-23" => Positive,
864		sign_pos_05: "0.00001" => Positive
865	}
866
867	sign_tests! {
868		sign_neg_01: "-1" => Negative,
869		sign_neg_02: "-0.1" => Negative,
870		sign_neg_03: "-0.01e23" => Negative,
871		sign_neg_04: "-1.0E-23" => Negative,
872		sign_neg_05: "-0.00001" => Negative
873	}
874
875	canonical_tests! {
876		canonical_01: "-0.0000" => "0",
877		canonical_02: "0.00000000028" => "2.8e-10"
878	}
879}