core_json_traits/
float.rs

1use core::num::FpCategory;
2use crate::{BytesLike, Stack, JsonError, Value, JsonDeserialize, JsonSerialize};
3
4impl JsonDeserialize for f64 {
5  fn deserialize<'bytes, 'parent, B: BytesLike<'bytes>, S: Stack>(
6    value: Value<'bytes, 'parent, B, S>,
7  ) -> Result<Self, JsonError<'bytes, B, S>> {
8    value.as_f64()
9  }
10}
11
12/// A JSON-compatible `f64`.
13///
14/// JSON does not support representing `NaN`, `inf`, but rather only well-defined values. This
15/// ensures the `f64` is representable within JSON. We additionally limit to normal `f64`s to
16/// achieve certain bounds.
17#[derive(Clone, Copy, Default, Debug)]
18pub struct JsonF64(f64);
19
20impl TryFrom<f64> for JsonF64 {
21  type Error = FpCategory;
22  fn try_from(value: f64) -> Result<Self, Self::Error> {
23    let class = value.classify();
24    match class {
25      FpCategory::Nan | FpCategory::Infinite => Err(class)?,
26      FpCategory::Zero | FpCategory::Normal | FpCategory::Subnormal => {}
27    }
28    Ok(Self(value))
29  }
30}
31
32impl From<JsonF64> for f64 {
33  fn from(value: JsonF64) -> f64 {
34    value.0
35  }
36}
37
38impl JsonDeserialize for JsonF64 {
39  fn deserialize<'bytes, 'parent, B: BytesLike<'bytes>, S: Stack>(
40    value: Value<'bytes, 'parent, B, S>,
41  ) -> Result<Self, JsonError<'bytes, B, S>> {
42    JsonF64::try_from(f64::deserialize(value)?).map_err(|_| JsonError::TypeError)
43  }
44}
45
46#[cfg(not(feature = "ryu"))]
47mod serialize {
48  use core::fmt::Write;
49  use super::*;
50
51  // A non-allocating buffer for `core::fmt::Write` which truncates after `f64::DIGITS` digits
52  struct Buffer {
53    // sign, significant digits, decimal point
54    // For more information, please see `core_json::Value::as_f64`
55    bytes: [u8; 1 + (f64::DIGITS as usize) + 1],
56    i: usize,
57    digits: usize,
58    before_decimal: bool,
59    omitted_decimal: bool,
60    before_exponent: bool,
61    negative_exponent: bool,
62    exponent: i16,
63    exponent_correction: i16,
64  }
65  impl Write for Buffer {
66    fn write_str(&mut self, value: &str) -> core::fmt::Result {
67      for char in value.chars() {
68        if self.before_exponent {
69          if char.is_ascii_digit() {
70            // Stop writing digits after we have all the significant digits which can be definitely
71            // handled without deviations when converting to/from a decimal string
72            if self.digits == (f64::DIGITS as usize) {
73              // If we're truncating prior to the decimal, mark a correction to the exponent
74              if self.before_decimal {
75                self.exponent_correction += 1;
76              }
77              continue;
78            }
79
80            // If this is a digit when we've omitted the decimal, correct the exponent
81            if self.omitted_decimal {
82              self.exponent_correction -= 1;
83            }
84
85            // Skip leading zeroes
86            if (self.digits == 0) && (char == '0') {
87              continue;
88            }
89
90            // Include the digit
91            self.digits += 1;
92          }
93
94          if char == '.' {
95            self.before_decimal = false;
96            // Don't include a decimal if we have yet to include the value itself
97            if self.digits == 0 {
98              self.omitted_decimal = true;
99              continue;
100            }
101            // Don't include a decimal point if we won't include the following digits
102            if self.digits == (f64::DIGITS as usize) {
103              continue;
104            }
105          }
106        }
107
108        if matches!(char, 'e' | 'E') {
109          self.before_exponent = false;
110          continue;
111        }
112
113        if self.before_exponent {
114          // Safe as `f64`'s display will only contain ASCII characters
115          self.bytes[self.i] = char as u8;
116          self.i += 1;
117        } else {
118          // Assumes exponents will be represented as `[ plus / minus ] *DIGIT` and fit within an
119          // `i16` (which they should as `f64::MAX_10_EXP = 308`)
120          if char == '+' {
121            continue;
122          }
123          if char == '-' {
124            self.negative_exponent = true;
125            continue;
126          }
127          self.exponent *= 10;
128          self.exponent += i16::from((char as u8) - b'0');
129        }
130      }
131      Ok(())
132    }
133  }
134
135  impl JsonSerialize for JsonF64 {
136    /// This will only serialize the `f64::DIGITS` most significant digits.
137    fn serialize(&self) -> impl Iterator<Item = char> {
138      let mut buffer = Buffer {
139        bytes: [b'0'; _],
140        i: 0,
141        digits: 0,
142        before_decimal: true,
143        omitted_decimal: false,
144        before_exponent: true,
145        negative_exponent: false,
146        exponent: 0,
147        exponent_correction: 0,
148      };
149      write!(&mut buffer, "{:?}", self.0).expect("infallible buffer raised an error");
150
151      // If Rust gave us `1.e` (invalid), decrement the buffer index to remove the '.'
152      if buffer.i.checked_sub(1).map(|i| buffer.bytes[i]) == Some(b'.') {
153        buffer.i -= 1;
154      }
155
156      let exponent = {
157        let exponent = (if buffer.negative_exponent { -buffer.exponent } else { buffer.exponent }) +
158          buffer.exponent_correction;
159
160        ((buffer.i != 0) && (exponent != 0))
161          .then(|| core::iter::once('e').chain(crate::primitives::i64_to_str(exponent)))
162          .into_iter()
163          .flatten()
164      };
165      // Safe as all of the written-to values will be written-to with ASCII characters
166      buffer.bytes.into_iter().take(buffer.i.max(1)).map(|b| b as char).chain(exponent)
167    }
168  }
169
170  #[test]
171  fn f64_serialize() {
172    use core::str::FromStr;
173    #[allow(clippy::float_cmp)]
174    let test = |value: f64, expected| {
175      assert_eq!(
176        f64::from_str(&JsonF64::try_from(value).unwrap().serialize().collect::<String>()).unwrap(),
177        f64::from_str(expected).unwrap()
178      );
179    };
180    test(0.0, "0");
181    test(0.1, "1e-1");
182    test(0.01, "1e-2");
183    test(0.001, "1e-3");
184    test(0.0012, "12e-4");
185    test(0.12345678910111213, "123456789101112e-15");
186    test(0.012345678910111213, "123456789101112e-16");
187    test(12345678910111213.0, "123456789101112e2");
188    test(12345678910111213.123, "123456789101112e2");
189    test(123456789.101112, "123456789.101112");
190    test(123456789.10111213, "123456789.101112");
191    test(-1.0, "-1");
192    test(f64::MIN, "-179769313486231e294");
193    test(f64::MAX, "179769313486231e294");
194    test(f64::EPSILON, "222044604925031e-30");
195  }
196}
197
198#[cfg(feature = "ryu")]
199mod serialize {
200  use super::*;
201
202  impl JsonSerialize for JsonF64 {
203    fn serialize(&self) -> impl Iterator<Item = char> {
204      let mut buffer = ryu::Buffer::new();
205      // Safe as `JsonF64` ensures this isn't `NaN`, `inf`
206      let result = buffer.format_finite(self.0).as_bytes();
207      /*
208        `ryu` yields us a string slice when we need an owned value to iterate, unfortunately, so
209        we copy the yielded string (a reference to the Buffer) into our own buffer (of equivalent
210        size)
211      */
212      let mut owned = [0; core::mem::size_of::<ryu::Buffer>()];
213      owned[.. result.len()].copy_from_slice(result);
214      // Safe to cast to char as `ryu` yields human-readable ASCII characters
215      owned.into_iter().take(result.len()).map(|byte| byte as char)
216    }
217  }
218}