Skip to main content

json_syntax/
number.rs

1use std::borrow::{Borrow, ToOwned};
2use std::fmt;
3use std::hash::Hash;
4use std::ops::Deref;
5use std::str::FromStr;
6
7use smallvec::SmallVec;
8use str_newtype::Buffer;
9
10use crate::lexical::JsonBorrowLexical;
11
12pub const JSON_NUMBER_RAW_BUFFER_CAPACITY: usize = 8;
13
14pub type JsonNumberRawBuffer = SmallVec<[u8; JSON_NUMBER_RAW_BUFFER_CAPACITY]>;
15
16/// Invalid number error.
17///
18/// The inner value is the data failed to be parsed.
19#[derive(Clone, Copy, Debug)]
20pub struct InvalidJsonNumber<T = String>(pub T);
21
22impl<T: fmt::Display> fmt::Display for InvalidJsonNumber<T> {
23	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24		write!(f, "invalid JSON number: {}", self.0)
25	}
26}
27
28impl<T: fmt::Display + fmt::Debug> std::error::Error for InvalidJsonNumber<T> {}
29
30/// Number sign.
31#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
32pub enum Sign {
33	Negative,
34	Zero,
35	Positive,
36}
37
38impl Sign {
39	/// Checks if the number is zero.
40	#[inline(always)]
41	pub fn is_zero(&self) -> bool {
42		matches!(self, Self::Zero)
43	}
44
45	/// Checks if the number is non positive (negative or zero).
46	#[inline(always)]
47	pub fn is_non_positive(&self) -> bool {
48		matches!(self, Self::Negative | Self::Zero)
49	}
50
51	/// Checks if the number is non negative (positive or zero).
52	#[inline(always)]
53	pub fn is_non_negative(&self) -> bool {
54		matches!(self, Self::Positive | Self::Zero)
55	}
56
57	/// Checks if the number is strictly positive (non zero nor negative).
58	#[inline(always)]
59	pub fn is_positive(&self) -> bool {
60		matches!(self, Self::Positive)
61	}
62
63	/// Checks if the number is strictly negative (non zero nor positive).
64	#[inline(always)]
65	pub fn is_negative(&self) -> bool {
66		matches!(self, Self::Negative)
67	}
68}
69
70/// Lexical JSON number.
71///
72/// This hold the lexical representation of a JSON number.
73/// All the comparison operations are done on this *lexical* representation,
74/// meaning that `1` is actually greater than `0.1e+80` for instance.
75#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
76pub struct JsonNumber {
77	data: [u8],
78}
79
80impl JsonNumber {
81	/// Creates a new number by parsing the given input `data`.
82	pub fn new<B: AsRef<[u8]> + ?Sized>(data: &B) -> Result<&Self, InvalidJsonNumber<&B>> {
83		let s = data.as_ref();
84		if Self::validate_bytes(s) {
85			Ok(unsafe { Self::new_unchecked(s) })
86		} else {
87			Err(InvalidJsonNumber(data))
88		}
89	}
90
91	/// Creates a new number without parsing the given input `data`.
92	///
93	/// ## Safety
94	///
95	/// The `data` input **must** be a valid JSON number.
96	#[inline(always)]
97	pub unsafe fn new_unchecked<B: AsRef<[u8]> + ?Sized>(data: &B) -> &JsonNumber {
98		unsafe { std::mem::transmute(data.as_ref()) }
99	}
100
101	pub fn validate_bytes(s: &[u8]) -> bool {
102		enum State {
103			Init,
104			FirstDigit,
105			Zero,
106			NonZero,
107			FractionalFirst,
108			FractionalRest,
109			ExponentSign,
110			ExponentFirst,
111			ExponentRest,
112		}
113
114		let mut state = State::Init;
115
116		for b in s {
117			match state {
118				State::Init => match *b {
119					b'-' => state = State::FirstDigit,
120					b'0' => state = State::Zero,
121					b'1'..=b'9' => state = State::NonZero,
122					_ => return false,
123				},
124				State::FirstDigit => match *b {
125					b'0' => state = State::Zero,
126					b'1'..=b'9' => state = State::NonZero,
127					_ => return false,
128				},
129				State::Zero => match *b {
130					b'.' => state = State::FractionalFirst,
131					b'e' | b'E' => state = State::ExponentSign,
132					_ => return false,
133				},
134				State::NonZero => match *b {
135					b'0'..=b'9' => state = State::NonZero,
136					b'.' => state = State::FractionalFirst,
137					b'e' | b'E' => state = State::ExponentSign,
138					_ => return false,
139				},
140				State::FractionalFirst => match *b {
141					b'0'..=b'9' => state = State::FractionalRest,
142					_ => return false,
143				},
144				State::FractionalRest => match *b {
145					b'0'..=b'9' => state = State::FractionalRest,
146					b'e' | b'E' => state = State::ExponentSign,
147					_ => return false,
148				},
149				State::ExponentSign => match *b {
150					b'+' | b'-' => state = State::ExponentFirst,
151					b'0'..=b'9' => state = State::ExponentRest,
152					_ => return false,
153				},
154				State::ExponentFirst => match *b {
155					b'0'..=b'9' => state = State::ExponentRest,
156					_ => return false,
157				},
158				State::ExponentRest => match *b {
159					b'0'..=b'9' => state = State::ExponentRest,
160					_ => return false,
161				},
162			}
163		}
164
165		matches!(
166			state,
167			State::Zero | State::NonZero | State::FractionalRest | State::ExponentRest
168		)
169	}
170
171	#[inline(always)]
172	pub fn as_str(&self) -> &str {
173		unsafe {
174			// safe because `self.data` is always a valid UTF-8 sequence.
175			std::str::from_utf8_unchecked(&self.data)
176		}
177	}
178
179	/// Trims unnecessary trailing decimal point and zeros.
180	///
181	/// Also removes a zero exponent (e.g. `e0`, `E+0`) and the negative sign
182	/// when the number is zero (e.g. `-0`, `-0.0`).
183	pub fn trimmed(&self) -> &Self {
184		let data = &self.data;
185
186		// Find the start of the exponent part, if any.
187		let exp_pos = data.iter().position(|&b| b == b'e' || b == b'E');
188		let mantissa_end = exp_pos.unwrap_or(data.len());
189
190		// Trim trailing fractional zeros from the mantissa only.
191		let mut trim_end = 1;
192		let mut in_frac = false;
193		for (i, b) in data.iter().copied().enumerate().take(mantissa_end).skip(1) {
194			match b {
195				b'0' if in_frac => (), // trailing fractional zero: drop
196				b'.' => in_frac = true,
197				_ => trim_end = i + 1,
198			}
199		}
200
201		// Determine whether the exponent is zero (so it can be dropped).
202		let zero_exp = exp_pos.is_none_or(|e| {
203			let after_e = &data[e + 1..];
204			let digits = if after_e.first().is_some_and(|&b| b == b'+' || b == b'-') {
205				&after_e[1..]
206			} else {
207				after_e
208			};
209			digits.iter().all(|&b| b == b'0')
210		});
211
212		// If the exponent is non-zero we must include it, but we can only return
213		// a contiguous subslice, so fractional trimming cannot be combined with a
214		// kept exponent. In that case fall back to the original data.
215		let end = if zero_exp {
216			trim_end
217		} else {
218			// mantissa was not trimmed; or
219			// cannot trim fractional zeros and keep exponent in one slice.
220			data.len()
221		};
222
223		// Strip the leading `-` when the number is zero.
224		let start = if data[0] == b'-' && &data[1..trim_end] == b"0" {
225			1
226		} else {
227			0
228		};
229
230		unsafe { Self::new_unchecked(&data[start..end]) }
231	}
232
233	/// Checks if the number is equal to zero (`0`).
234	///
235	/// This include every lexical representation where
236	/// the decimal and fraction part are composed of only
237	/// `0`, maybe preceded with `-`, and an arbitrary exponent part.
238	#[inline(always)]
239	pub fn is_zero(&self) -> bool {
240		for b in &self.data {
241			match b {
242				b'-' | b'0' | b'.' => (),
243				b'e' | b'E' => break,
244				_ => return false,
245			}
246		}
247
248		true
249	}
250
251	/// Returns the sign of the number.
252	pub fn sign(&self) -> Sign {
253		let mut non_negative = true;
254
255		for b in &self.data {
256			match b {
257				b'-' => non_negative = false,
258				b'0' | b'.' => (),
259				b'e' | b'E' => break,
260				_ => {
261					return if non_negative {
262						Sign::Positive
263					} else {
264						Sign::Negative
265					};
266				}
267			}
268		}
269
270		Sign::Zero
271	}
272
273	/// Checks if the number is non positive (negative or zero).
274	#[inline(always)]
275	pub fn is_non_positive(&self) -> bool {
276		self.sign().is_non_positive()
277	}
278
279	/// Checks if the number is non negative (positive or zero).
280	#[inline(always)]
281	pub fn is_non_negative(&self) -> bool {
282		self.sign().is_non_negative()
283	}
284
285	/// Checks if the number is strictly positive (non zero nor negative).
286	#[inline(always)]
287	pub fn is_positive(&self) -> bool {
288		self.sign().is_positive()
289	}
290
291	/// Checks if the number is strictly negative (non zero nor positive).
292	#[inline(always)]
293	pub fn is_negative(&self) -> bool {
294		self.sign().is_negative()
295	}
296
297	/// Checks if the number has a decimal point.
298	#[inline(always)]
299	pub fn has_decimal_point(&self) -> bool {
300		self.data.contains(&b'.')
301	}
302
303	/// Checks if the number has a fraction part.
304	///
305	/// This is an alias for [`has_decimal_point`](Self::has_decimal_point).
306	#[inline(always)]
307	pub fn has_fraction(&self) -> bool {
308		self.has_decimal_point()
309	}
310
311	/// Checks if the number has an exponent part.
312	#[inline(always)]
313	pub fn has_exponent(&self) -> bool {
314		for b in &self.data {
315			if matches!(b, b'e' | b'E') {
316				return true;
317			}
318		}
319
320		false
321	}
322
323	#[inline(always)]
324	pub fn is_i32(&self) -> bool {
325		self.as_i32().is_some()
326	}
327
328	#[inline(always)]
329	pub fn is_i64(&self) -> bool {
330		self.as_i64().is_some()
331	}
332
333	#[inline(always)]
334	pub fn is_u32(&self) -> bool {
335		self.as_u32().is_some()
336	}
337
338	#[inline(always)]
339	pub fn is_u64(&self) -> bool {
340		self.as_u64().is_some()
341	}
342
343	#[inline(always)]
344	pub fn as_i32(&self) -> Option<i32> {
345		self.as_str().parse().ok()
346	}
347
348	#[inline(always)]
349	pub fn as_i64(&self) -> Option<i64> {
350		self.as_str().parse().ok()
351	}
352
353	#[inline(always)]
354	pub fn as_u32(&self) -> Option<u32> {
355		self.as_str().parse().ok()
356	}
357
358	#[inline(always)]
359	pub fn as_u64(&self) -> Option<u64> {
360		self.as_str().parse().ok()
361	}
362
363	#[inline(always)]
364	pub fn as_f32_lossy(&self) -> f32 {
365		lexical::parse_with_options::<_, _, { lexical::format::JSON }>(
366			self.as_bytes(),
367			&LOSSY_PARSE_FLOAT,
368		)
369		.unwrap()
370	}
371
372	/// Returns the number as a `f32` only if the operation does not induce
373	/// imprecisions/approximations.
374	///
375	/// This operation is expensive as it requires allocating a new number
376	/// buffer to check the decimal representation of the generated `f32`.
377	#[inline(always)]
378	pub fn as_f32_lossless(&self) -> Option<f32> {
379		let f = self.as_f32_lossy();
380		let n: JsonNumberBuf = f.try_into().unwrap();
381		if n.as_number() == self.trimmed() {
382			Some(f)
383		} else {
384			None
385		}
386	}
387
388	#[inline(always)]
389	pub fn as_f64_lossy(&self) -> f64 {
390		lexical::parse_with_options::<_, _, { lexical::format::JSON }>(
391			self.as_bytes(),
392			&LOSSY_PARSE_FLOAT,
393		)
394		.unwrap()
395	}
396
397	/// Returns the number as a `f64` only if the operation does not induce
398	/// imprecisions/approximations.
399	///
400	/// This operation is expensive as it requires allocating a new number
401	/// buffer to check the decimal representation of the generated `f64`.
402	#[inline(always)]
403	pub fn as_f64_lossless(&self) -> Option<f64> {
404		let f = self.as_f64_lossy();
405		let n: JsonNumberBuf = f.try_into().unwrap();
406		if n.as_number() == self { Some(f) } else { None }
407	}
408
409	/// Returns the canonical representation of this number according to
410	/// [RFC8785](https://www.rfc-editor.org/rfc/rfc8785#name-serialization-of-numbers).
411	#[cfg(feature = "canonicalize")]
412	pub fn canonicalized_with<'b>(&'b self, buffer: &'b mut ryu_js::Buffer) -> &'b JsonNumber {
413		let f = self.as_f64_lossy();
414
415		if f.is_finite() {
416			unsafe { JsonNumber::new_unchecked(buffer.format_finite(self.as_f64_lossy())) }
417		} else {
418			self
419		}
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 = "canonicalize")]
425	pub fn canonicalized(&self) -> JsonNumberBuf {
426		let mut buffer = ryu_js::Buffer::new();
427		self.canonicalized_with(&mut buffer).to_owned()
428	}
429}
430
431impl JsonBorrowLexical for JsonNumber {}
432
433impl JsonBorrowLexical for JsonNumberBuf {}
434
435const LOSSY_PARSE_FLOAT: lexical::ParseFloatOptions = lexical::ParseFloatOptions::builder()
436	.lossy(true)
437	.build_unchecked();
438
439impl Deref for JsonNumber {
440	type Target = str;
441
442	#[inline(always)]
443	fn deref(&self) -> &str {
444		self.as_str()
445	}
446}
447
448impl AsRef<str> for JsonNumber {
449	#[inline(always)]
450	fn as_ref(&self) -> &str {
451		self.as_str()
452	}
453}
454
455impl Borrow<str> for JsonNumber {
456	#[inline(always)]
457	fn borrow(&self) -> &str {
458		self.as_str()
459	}
460}
461
462impl AsRef<[u8]> for JsonNumber {
463	#[inline(always)]
464	fn as_ref(&self) -> &[u8] {
465		self.as_bytes()
466	}
467}
468
469impl<'a> TryFrom<&'a str> for &'a JsonNumber {
470	type Error = InvalidJsonNumber<&'a str>;
471
472	#[inline(always)]
473	fn try_from(s: &'a str) -> Result<&'a JsonNumber, InvalidJsonNumber<&'a str>> {
474		JsonNumber::new(s)
475	}
476}
477
478impl ToOwned for JsonNumber {
479	type Owned = JsonNumberBuf;
480
481	fn to_owned(&self) -> Self::Owned {
482		unsafe { JsonNumberBuf::new_unchecked(self.as_bytes().into()) }
483	}
484}
485
486impl fmt::Display for JsonNumber {
487	#[inline(always)]
488	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
489		self.as_str().fmt(f)
490	}
491}
492
493impl fmt::Debug for JsonNumber {
494	#[inline(always)]
495	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
496		self.as_str().fmt(f)
497	}
498}
499
500/// JSON number buffer.
501#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
502pub struct JsonNumberBuf {
503	data: JsonNumberRawBuffer,
504}
505
506impl JsonNumberBuf {
507	/// Creates a new number buffer by parsing the given input `data` buffer.
508	#[inline(always)]
509	pub fn new<T: Buffer>(data: T) -> Result<Self, InvalidJsonNumber<T>> {
510		match JsonNumber::new(data.as_bytes()) {
511			Ok(_) => Ok(JsonNumberBuf {
512				data: data.into_bytes().into(),
513			}),
514			Err(_) => Err(InvalidJsonNumber(data)),
515		}
516	}
517
518	/// Creates a new number buffer from the given input `data` buffer.
519	///
520	/// ## Safety
521	///
522	/// The input `data` **must** hold a valid JSON number string.
523	#[inline(always)]
524	pub unsafe fn new_unchecked(data: JsonNumberRawBuffer) -> Self {
525		JsonNumberBuf { data }
526	}
527
528	/// Creates a number buffer from the given `number`.
529	#[inline(always)]
530	pub fn from_number(n: &JsonNumber) -> Self {
531		unsafe { JsonNumberBuf::new_unchecked(n.as_bytes().into()) }
532	}
533
534	#[inline(always)]
535	pub fn as_raw_buffer(&self) -> &JsonNumberRawBuffer {
536		&self.data
537	}
538
539	#[inline(always)]
540	pub fn into_raw_buffer(self) -> JsonNumberRawBuffer {
541		self.data
542	}
543}
544
545impl JsonNumberBuf {
546	/// Puts this number in canonical form according to
547	/// [RFC8785](https://www.rfc-editor.org/rfc/rfc8785#name-serialization-of-numbers).
548	#[cfg(feature = "canonicalize")]
549	pub fn canonicalize(&mut self) {
550		*self = self.canonicalized();
551	}
552
553	/// Puts this number in canonical form according to
554	/// [RFC8785](https://www.rfc-editor.org/rfc/rfc8785#name-serialization-of-numbers).
555	#[cfg(feature = "canonicalize")]
556	pub fn canonicalize_with(&mut self, buffer: &mut ryu_js::Buffer) {
557		*self = self.canonicalized_with(buffer).to_owned()
558	}
559}
560
561impl JsonNumberBuf {
562	#[inline(always)]
563	pub fn into_string(self) -> String {
564		unsafe { String::from_utf8_unchecked(self.into_bytes()) }
565	}
566
567	#[inline(always)]
568	pub fn into_bytes(self) -> Vec<u8> {
569		self.data.into_vec()
570	}
571}
572
573impl JsonNumberBuf {
574	#[inline(always)]
575	pub fn as_number(&self) -> &JsonNumber {
576		unsafe { JsonNumber::new_unchecked(&self.data) }
577	}
578}
579
580impl FromStr for JsonNumberBuf {
581	type Err = InvalidJsonNumber;
582
583	#[inline(always)]
584	fn from_str(s: &str) -> Result<Self, Self::Err> {
585		Self::new(s.to_owned())
586	}
587}
588
589impl Deref for JsonNumberBuf {
590	type Target = JsonNumber;
591
592	#[inline(always)]
593	fn deref(&self) -> &JsonNumber {
594		self.as_number()
595	}
596}
597
598impl AsRef<JsonNumber> for JsonNumberBuf {
599	#[inline(always)]
600	fn as_ref(&self) -> &JsonNumber {
601		self.as_number()
602	}
603}
604
605impl Borrow<JsonNumber> for JsonNumberBuf {
606	#[inline(always)]
607	fn borrow(&self) -> &JsonNumber {
608		self.as_number()
609	}
610}
611
612impl AsRef<str> for JsonNumberBuf {
613	#[inline(always)]
614	fn as_ref(&self) -> &str {
615		self.as_str()
616	}
617}
618
619impl AsRef<[u8]> for JsonNumberBuf {
620	#[inline(always)]
621	fn as_ref(&self) -> &[u8] {
622		self.as_bytes()
623	}
624}
625
626impl fmt::Display for JsonNumberBuf {
627	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
628		self.as_str().fmt(f)
629	}
630}
631
632impl fmt::Debug for JsonNumberBuf {
633	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
634		self.as_str().fmt(f)
635	}
636}
637
638macro_rules! impl_from_int {
639	($($ty:ty),*) => {
640		$(
641			impl From<$ty> for JsonNumberBuf {
642				#[inline(always)]
643				fn from(i: $ty) -> Self {
644					unsafe {
645						Self::new_unchecked(JsonNumberRawBuffer::from_vec(lexical::to_string(i).into_bytes()))
646					}
647				}
648			}
649		)*
650	};
651}
652
653/// Float conversion error.
654#[derive(Clone, Copy, Debug)]
655pub enum TryFromFloatError {
656	/// The float was Nan, which is not a JSON number.
657	Nan,
658
659	/// The float was not finite, and hence not a JSON number.
660	Infinite,
661}
662
663const WRITE_FLOAT: lexical::WriteFloatOptions = lexical::WriteFloatOptions::builder()
664	.trim_floats(true)
665	.exponent(b'e')
666	.build_unchecked();
667
668macro_rules! impl_try_from_float {
669	($($ty:ty),*) => {
670		$(
671			impl TryFrom<$ty> for JsonNumberBuf {
672				type Error = TryFromFloatError;
673
674				#[inline(always)]
675				fn try_from(f: $ty) -> Result<Self, Self::Error> {
676					if f.is_finite() {
677						Ok(unsafe {
678							Self::new_unchecked(JsonNumberRawBuffer::from_vec(lexical::to_string_with_options::<_, {lexical::format::JSON}>(f, &WRITE_FLOAT).into_bytes()))
679						})
680					} else if f.is_nan() {
681						Err(TryFromFloatError::Nan)
682					} else {
683						Err(TryFromFloatError::Infinite)
684					}
685				}
686			}
687		)*
688	};
689}
690
691impl_from_int!(u8, i8, u16, i16, u32, i32, u64, i64, usize, isize);
692impl_try_from_float!(f32, f64);
693
694#[cfg(test)]
695mod tests {
696	use super::*;
697
698	fn trimming_test(a: &str, b: &str) {
699		let a = JsonNumber::new(a).unwrap();
700		let b = JsonNumber::new(b).unwrap();
701		assert_eq!(a.trimmed(), b)
702	}
703
704	#[test]
705	fn trimming() {
706		// Basic fractional trimming (existing cases).
707		trimming_test("0", "0");
708		trimming_test("0.0", "0");
709		trimming_test("1.0", "1");
710		trimming_test("1.1", "1.1");
711		trimming_test("1.10000", "1.1");
712		trimming_test("100.0", "100");
713		trimming_test("100.1000", "100.1");
714
715		// Negative sign removed for zero values.
716		trimming_test("-0", "0");
717		trimming_test("-0.0", "0");
718		trimming_test("-0.000", "0");
719
720		// Zero exponent removed.
721		trimming_test("0e0", "0");
722		trimming_test("1e0", "1");
723		trimming_test("1.0e0", "1");
724		trimming_test("1.5e0", "1.5");
725		trimming_test("100.0e0", "100");
726
727		// Zero exponent and negative sign both removed.
728		trimming_test("-0e0", "0");
729		trimming_test("-0.0e0", "0");
730		trimming_test("-0.000e0", "0");
731
732		// Non-zero exponent kept unchanged.
733		trimming_test("1e21", "1e21");
734		trimming_test("1.5e2", "1.5e2");
735		trimming_test("-1e5", "-1e5");
736
737		// Trailing zeros with non-zero exponent: fractional zeros cannot be
738		// removed because the result would not be a contiguous subslice.
739		trimming_test("1.0e2", "1.0e2");
740		trimming_test("1.50e2", "1.50e2");
741		trimming_test("10.0e2", "10.0e2");
742	}
743
744	macro_rules! positive_tests {
745		{ $($id:ident: $input:literal),* } => {
746			$(
747				#[test]
748				fn $id () {
749					assert!(JsonNumber::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!(JsonNumber::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!(JsonNumber::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="canonicalize")]
781				#[test]
782				fn $id () {
783					assert_eq!(JsonNumber::new($input).unwrap().canonicalized().as_number(), JsonNumber::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}