json_number/
lib.rs

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