json_number/
lib.rs

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