Skip to main content

rupg_types/
float.rs

1//! `float4` and `float8` in the text format.
2//!
3//! The output follows `float4out` and `float8out_internal` in `src/backend/utils/adt/float.c`. When `extra_float_digits` is more than 0, which is the default, the output is the shortest text that reads back to the same bits. PostgreSQL uses Ryu for the digits and its own layout in `to_chars` of `src/common/d2s.c` and `f2s.c`: fixed notation when the decimal exponent is from -4 to 14 for `float8` and from -4 to 5 for `float4`, else a mantissa, `e`, a sign and at least two digits. The standard library gives the same shortest digits with `{:e}`, with one difference. The standard library takes a text that is exactly halfway to the next value when the halfway text reads back to the value, but Ryu in PostgreSQL never takes a halfway text. So `1e23::float8` is `9.999999999999999e+22`. This module finds those texts with exact integer arithmetic and then takes the shortest text that is not halfway. When the value is exactly halfway between two shortest texts, Ryu takes the text with the even last digit and the standard library takes the text above, so this module takes the even text too. It puts the digits in the layout of PostgreSQL. When `extra_float_digits` is 0 or less, the output is `%.*g` with `DBL_DIG` or `FLT_DIG` plus `extra_float_digits` digits, as `pg_strfromd` makes it.
4//!
5//! The input follows `float8in_internal` and `float4in_internal`, which call `strtod` and `strtof`. So the input takes what the C library takes: decimal and hexadecimal numbers, `inf`, `infinity` and `nan` in any case, and `nan(...)`. A number that is too large, or a number that is not zero but reads as zero, is out of range.
6//!
7//! Lifted from `crates/rudb-pgtypes/src/float.rs` of tamnd/rudb at f5f7065a (spec/04 section 4.9).
8
9use std::fmt::Write as _;
10
11use rupg_common::SqlState;
12
13use crate::error::TypeError;
14use crate::number::is_space;
15
16/// A small buffer on the stack for the standard formatter, so that the output of one value does not allocate.
17struct Stack {
18    buf: [u8; 64],
19    len: usize,
20}
21
22impl Stack {
23    fn new() -> Stack {
24        Stack { buf: [0; 64], len: 0 }
25    }
26
27    fn bytes(&self) -> &[u8] {
28        &self.buf[..self.len]
29    }
30}
31
32impl std::fmt::Write for Stack {
33    fn write_str(&mut self, s: &str) -> std::fmt::Result {
34        let end = self.len + s.len();
35        self.buf.get_mut(self.len..end).ok_or(std::fmt::Error)?.copy_from_slice(s.as_bytes());
36        self.len = end;
37        Ok(())
38    }
39}
40
41/// Splits the output of `{:e}` into the digits with no point and the decimal exponent of the first digit.
42fn split_exp(text: &[u8]) -> (&[u8], i32) {
43    let e = text.iter().position(|&b| b == b'e').unwrap_or(text.len());
44    let exp = std::str::from_utf8(&text[e + 1..]).ok().and_then(|t| t.parse().ok()).unwrap_or(0);
45    (&text[..e], exp)
46}
47
48/// Writes `e`, the sign and at least two digits of the exponent.
49fn exponent(exp: i32, out: &mut Vec<u8>) {
50    out.push(b'e');
51    out.push(if exp < 0 { b'-' } else { b'+' });
52    let exp = exp.unsigned_abs();
53    if exp < 10 {
54        out.push(b'0');
55    }
56    crate::number::u64_out(u64::from(exp), out);
57}
58
59/// Writes the digits `d1 d2 ... dn` times 10 to the power `exp` in fixed notation, with no trailing zeros after the point and no point when there is no fraction.
60fn fixed(digits: &[u8], exp: i32, out: &mut Vec<u8>) {
61    let n = digits.len() as i32;
62    if exp < 0 {
63        out.extend_from_slice(b"0.");
64        out.extend(std::iter::repeat_n(b'0', (-exp - 1) as usize));
65        out.extend_from_slice(digits);
66    } else if exp + 1 >= n {
67        out.extend_from_slice(digits);
68        out.extend(std::iter::repeat_n(b'0', (exp + 1 - n) as usize));
69    } else {
70        let point = (exp + 1) as usize;
71        out.extend_from_slice(&digits[..point]);
72        out.push(b'.');
73        out.extend_from_slice(&digits[point..]);
74    }
75}
76
77/// The digits of a `{:e}` mantissa with the point removed.
78fn plain(mantissa: &[u8]) -> Vec<u8> {
79    mantissa.iter().copied().filter(|&b| b != b'.').collect()
80}
81
82/// Writes the shortest text of a finite value that is not zero, in the layout of `to_chars`. `fixed_limit` is the first exponent that is not in fixed notation.
83fn shortest(sci: &[u8], fixed_limit: i32, out: &mut Vec<u8>) {
84    let (digits, exp) = split_exp(sci);
85    if (-4..fixed_limit).contains(&exp) {
86        fixed(&plain(digits), exp, out);
87    } else {
88        // The mantissa of `{:e}` is already `d.ddd`, or `d` with one digit.
89        out.extend_from_slice(digits);
90        exponent(exp, out);
91    }
92}
93
94/// The facts of the binary format that the shortest text needs.
95trait Shortest: Copy + PartialEq + std::fmt::LowerExp + std::str::FromStr {
96    /// The most significant digits that a value needs to read back.
97    const DIGITS: usize;
98    /// The value is `m` times 2 to the power `e`. The third part is true when the next value below is nearer than the next value above, which is so at a power of 2 that is not the smallest normal value.
99    fn parts(self) -> (u64, i32, bool);
100}
101
102impl Shortest for f64 {
103    const DIGITS: usize = 17;
104    fn parts(self) -> (u64, i32, bool) {
105        let bits = self.to_bits();
106        let (exp, frac) = ((bits >> 52) & 0x7ff, bits & ((1 << 52) - 1));
107        match exp {
108            0 => (frac, -1074, false),
109            _ => (frac | 1 << 52, exp as i32 - 1075, frac == 0 && exp > 1),
110        }
111    }
112}
113
114impl Shortest for f32 {
115    const DIGITS: usize = 9;
116    fn parts(self) -> (u64, i32, bool) {
117        let bits = self.to_bits();
118        let (exp, frac) = ((bits >> 23) & 0xff, bits & ((1 << 23) - 1));
119        match exp {
120            0 => (u64::from(frac), -149, false),
121            _ => (u64::from(frac | 1 << 23), exp as i32 - 150, frac == 0 && exp > 1),
122        }
123    }
124}
125
126/// True when `d` times 10 to the power `e10` is `c` times 2 to the power `e2`. The decimal is a sum of powers of 2 only when 5 to the power `-e10` divides `d`, and it has the factor 5 to the power `e10` that must divide `c`. Both `d` and `c` are less than 5 to the power 25, so the two are not equal when `e10` is outside -24 to 24, and in that range the products fit in `u128`.
127fn same_value(d: u64, e10: i32, c: u64, e2: i32) -> bool {
128    if !(-24..=24).contains(&e10) {
129        return false;
130    }
131    let left = u128::from(d) * 5u128.pow(e10.max(0).unsigned_abs());
132    let right = u128::from(c) * 5u128.pow(e10.min(0).unsigned_abs());
133    // left * 2^(e10 - e2) == right
134    let shift = e10 - e2;
135    let (small, large, shift) =
136        if shift >= 0 { (left, right, shift) } else { (right, left, -shift) };
137    shift < 128 && large.trailing_zeros() >= shift.unsigned_abs() && large >> shift == small
138}
139
140/// True when `d` times 10 to the power `e10` is exactly halfway between `v` and the next value above or below.
141fn halfway<F: Shortest>(v: F, d: u64, e10: i32) -> bool {
142    let (m, e, nearer_below) = v.parts();
143    let below = if nearer_below { (4 * m - 1, e - 2) } else { (2 * m - 1, e - 1) };
144    same_value(d, e10, 2 * m + 1, e - 1) || same_value(d, e10, below.0, below.1)
145}
146
147/// The digits of a `{:e}` text as an integer, and the decimal exponent of the last digit.
148fn decimal(sci: &[u8]) -> (u64, i32) {
149    let (digits, exp) = split_exp(sci);
150    let digits = plain(digits);
151    let d = digits.iter().fold(0u64, |d, &b| d * 10 + u64::from(b - b'0'));
152    (d, exp + 1 - digits.len() as i32)
153}
154
155/// The `{:e}` text of `d` times 10 to the power `e10`, with no trailing zeros.
156fn sci_text(mut d: u64, mut e10: i32) -> Stack {
157    while d.is_multiple_of(10) && d != 0 {
158        d /= 10;
159        e10 += 1;
160    }
161    let mut digits = Stack::new();
162    let _ = write!(digits, "{d}");
163    let digits = digits.bytes();
164    let mut sci = Stack::new();
165    let _ = sci.write_str(std::str::from_utf8(&digits[..1]).unwrap_or("0"));
166    if digits.len() > 1 {
167        let _ = sci.write_str(".");
168        let _ = sci.write_str(std::str::from_utf8(&digits[1..]).unwrap_or(""));
169    }
170    let _ = write!(sci, "e{}", e10 + digits.len() as i32 - 1);
171    sci
172}
173
174/// True when the text `d` times 10 to the power `e10` reads back to `v` and is not halfway.
175fn inside<F: Shortest>(v: F, d: u64, e10: i32) -> bool {
176    let mut text = Stack::new();
177    let _ = write!(text, "{d}e{e10}");
178    let reads_back = std::str::from_utf8(text.bytes()).ok().and_then(|t| t.parse::<F>().ok());
179    reads_back == Some(v) && !halfway(v, d, e10)
180}
181
182/// The `{:e}` text of the shortest digits of a positive finite value, as Ryu in PostgreSQL gives them.
183fn shortest_sci<F: Shortest>(v: F) -> Stack {
184    let mut sci = Stack::new();
185    let _ = write!(sci, "{v:e}");
186    let (d, e10) = decimal(sci.bytes());
187    if !halfway(v, d, e10) {
188        return even_tie(v, d, e10).map_or(sci, |d| sci_text(d, e10));
189    }
190    let len = split_exp(sci.bytes()).0.iter().filter(|&&b| b != b'.').count();
191    for len in len + 1..=F::DIGITS {
192        let mut near = Stack::new();
193        let _ = write!(near, "{:.*e}", len - 1, v);
194        let (d, e10) = decimal(near.bytes());
195        // The nearest text of this length can be halfway, and then the text on the other side of the value can be inside.
196        for d in [d, d + 1, d - 1] {
197            if inside(v, d, e10) {
198                return sci_text(even_tie(v, d, e10).unwrap_or(d), e10);
199            }
200        }
201    }
202    sci
203}
204
205/// When the value is exactly halfway between the odd digits `d` and the next digits, Ryu takes the even digits, but the standard library takes the digits above. Gives the even digits when they read back to the value.
206fn even_tie<F: Shortest>(v: F, d: u64, e10: i32) -> Option<u64> {
207    if d.is_multiple_of(2) {
208        return None;
209    }
210    let (m, e, _) = v.parts();
211    [(d - 1, 2 * d - 1), (d + 1, 2 * d + 1)]
212        .into_iter()
213        .find(|&(even, twice)| same_value(twice * 5, e10 - 1, m, e) && inside(v, even, e10))
214        .map(|(even, _)| even)
215}
216
217/// Writes `%.*g` of a finite value that is not zero, with `precision` significant digits.
218fn general(sci: &[u8], precision: i32, out: &mut Vec<u8>) {
219    let (digits, exp) = split_exp(sci);
220    let mut digits = plain(digits);
221    while digits.len() > 1 && digits.last() == Some(&b'0') {
222        digits.pop();
223    }
224    if exp < -4 || exp >= precision {
225        out.push(digits[0]);
226        if digits.len() > 1 {
227            out.push(b'.');
228            out.extend_from_slice(&digits[1..]);
229        }
230        exponent(exp, out);
231    } else {
232        fixed(&digits, exp, out);
233    }
234}
235
236/// Writes the sign and the special values. Returns `true` when the value is done.
237fn special(nan: bool, inf: bool, zero: bool, negative: bool, out: &mut Vec<u8>) -> bool {
238    if nan {
239        out.extend_from_slice(b"NaN");
240        return true;
241    }
242    if negative {
243        out.push(b'-');
244    }
245    if inf {
246        out.extend_from_slice(b"Infinity");
247    } else if zero {
248        out.push(b'0');
249    }
250    inf || zero
251}
252
253/// The text output of `float8`.
254pub fn float8_out(v: f64, extra_float_digits: i32, out: &mut Vec<u8>) {
255    if special(v.is_nan(), v.is_infinite(), v == 0.0, v.is_sign_negative(), out) {
256        return;
257    }
258    if extra_float_digits > 0 {
259        shortest(shortest_sci(v.abs()).bytes(), 15, out);
260        return;
261    }
262    let precision = (15 + extra_float_digits).clamp(1, 32);
263    let mut sci = Stack::new();
264    let _ = write!(sci, "{:.*e}", precision as usize - 1, v.abs());
265    general(sci.bytes(), precision, out);
266}
267
268/// The text output of `float4`.
269pub fn float4_out(v: f32, extra_float_digits: i32, out: &mut Vec<u8>) {
270    if special(v.is_nan(), v.is_infinite(), v == 0.0, v.is_sign_negative(), out) {
271        return;
272    }
273    if extra_float_digits > 0 {
274        shortest(shortest_sci(v.abs()).bytes(), 6, out);
275        return;
276    }
277    // `float4out` gives the value to `pg_strfromd` as a double.
278    let precision = (6 + extra_float_digits).clamp(1, 32);
279    let mut sci = Stack::new();
280    let _ = write!(sci, "{:.*e}", precision as usize - 1, f64::from(v.abs()));
281    general(sci.bytes(), precision, out);
282}
283
284/// The two float types for the input, with the facts of the IEEE format that a hexadecimal number needs.
285pub(crate) trait Float: Copy + std::str::FromStr {
286    /// The significant bits, with the hidden bit.
287    const BITS: u32;
288    /// The exponent of the last bit of the smallest subnormal value.
289    const MIN_EXP: i64;
290    /// The largest exponent of the first bit.
291    const MAX_EXP: i64;
292    const INFINITY: Self;
293    const NAN: Self;
294    fn from_parts(mantissa: u64, exp: i64) -> Self;
295    fn negate(self) -> Self;
296    fn is_zero(self) -> bool;
297    fn is_infinite(self) -> bool;
298}
299
300impl Float for f64 {
301    const BITS: u32 = 53;
302    const MIN_EXP: i64 = -1074;
303    const MAX_EXP: i64 = 1023;
304    const INFINITY: f64 = f64::INFINITY;
305    const NAN: f64 = f64::NAN;
306    fn from_parts(mantissa: u64, exp: i64) -> f64 {
307        if mantissa >> 52 == 0 {
308            return f64::from_bits(mantissa);
309        }
310        f64::from_bits(((exp + 52 + 1023) as u64) << 52 | (mantissa & ((1 << 52) - 1)))
311    }
312    fn negate(self) -> f64 {
313        -self
314    }
315    fn is_zero(self) -> bool {
316        self == 0.0
317    }
318    fn is_infinite(self) -> bool {
319        f64::is_infinite(self)
320    }
321}
322
323impl Float for f32 {
324    const BITS: u32 = 24;
325    const MIN_EXP: i64 = -149;
326    const MAX_EXP: i64 = 127;
327    const INFINITY: f32 = f32::INFINITY;
328    const NAN: f32 = f32::NAN;
329    fn from_parts(mantissa: u64, exp: i64) -> f32 {
330        if mantissa >> 23 == 0 {
331            return f32::from_bits(mantissa as u32);
332        }
333        f32::from_bits(((exp + 23 + 127) as u32) << 23 | (mantissa as u32 & ((1 << 23) - 1)))
334    }
335    fn negate(self) -> f32 {
336        -self
337    }
338    fn is_zero(self) -> bool {
339        self == 0.0
340    }
341    fn is_infinite(self) -> bool {
342        f32::is_infinite(self)
343    }
344}
345
346/// `mantissa` times 2 to the power `exp`, rounded to the nearest value with ties to even. `sticky` says that bits below the mantissa were not zero.
347fn from_binary<F: Float>(mantissa: u64, sticky: bool, exp: i64) -> F {
348    if mantissa == 0 {
349        return F::from_parts(0, 0);
350    }
351    let bits = i64::from(64 - mantissa.leading_zeros());
352    let mut e = (exp + bits - i64::from(F::BITS)).max(F::MIN_EXP);
353    let shift = e - exp;
354    let mut m = if shift <= 0 {
355        mantissa << -shift
356    } else if shift > 64 {
357        0
358    } else {
359        let kept = if shift == 64 { 0 } else { mantissa >> shift };
360        let lost = if shift == 64 { mantissa } else { mantissa & ((1 << shift) - 1) };
361        let half = 1u64 << (shift - 1);
362        let up = lost > half || (lost == half && (sticky || kept & 1 == 1));
363        kept + u64::from(up)
364    };
365    if m == 1 << F::BITS {
366        m >>= 1;
367        e += 1;
368    }
369    if e + i64::from(F::BITS) - 1 > F::MAX_EXP {
370        return F::INFINITY;
371    }
372    F::from_parts(m, e)
373}
374
375/// Reads a number as `strtod` does. Returns the value, the end of the number, and whether a mantissa that is not zero gave zero or a finite number gave infinity, which is `ERANGE` with a value that PostgreSQL refuses. Returns `None` when there is no number at the start.
376pub(crate) fn strtod<F: Float>(b: &[u8]) -> Option<(F, usize, bool)> {
377    let at = |i: usize| b.get(i).copied().unwrap_or(0);
378    let starts = |i: usize, word: &[u8]| {
379        b.get(i..i + word.len()).is_some_and(|s| s.eq_ignore_ascii_case(word))
380    };
381    let neg = at(0) == b'-';
382    let mut i = usize::from(neg || at(0) == b'+');
383    let sign = |v: F| if neg { v.negate() } else { v };
384
385    if starts(i, b"inf") {
386        let end = if starts(i, b"infinity") { i + 8 } else { i + 3 };
387        return Some((sign(F::INFINITY), end, false));
388    }
389    if starts(i, b"nan") {
390        let mut end = i + 3;
391        if at(end) == b'(' {
392            let mut j = end + 1;
393            while at(j).is_ascii_alphanumeric() || at(j) == b'_' {
394                j += 1;
395            }
396            if at(j) == b')' {
397                end = j + 1;
398            }
399        }
400        return Some((sign(F::NAN), end, false));
401    }
402
403    if at(i) == b'0' && matches!(at(i + 1), b'x' | b'X') {
404        let start = i + 2;
405        let mut j = start;
406        let (mut m, mut sticky, mut exp, mut taken, mut any) = (0u64, false, 0i64, 0, false);
407        let mut point = false;
408        loop {
409            let c = at(j);
410            if c == b'.' && !point {
411                point = true;
412            } else if let Some(d) = (c as char).to_digit(16) {
413                any = true;
414                if m == 0 && d == 0 {
415                    // A leading zero adds nothing, but after the point it moves the value.
416                    exp -= if point { 4 } else { 0 };
417                } else if taken < 16 {
418                    m = m << 4 | u64::from(d);
419                    taken += 1;
420                    exp -= if point { 4 } else { 0 };
421                } else {
422                    sticky |= d != 0;
423                    exp += if point { 0 } else { 4 };
424                }
425            } else {
426                break;
427            }
428            j += 1;
429        }
430        if any {
431            if matches!(at(j), b'p' | b'P') {
432                let mut k = j + 1;
433                let negative = at(k) == b'-';
434                k += usize::from(negative || at(k) == b'+');
435                if at(k).is_ascii_digit() {
436                    let mut power = 0i64;
437                    while at(k).is_ascii_digit() {
438                        power = (power * 10 + i64::from(at(k) - b'0')).min(1 << 40);
439                        k += 1;
440                    }
441                    exp += if negative { -power } else { power };
442                    j = k;
443                }
444            }
445            let value: F = from_binary(m, sticky, exp);
446            let range = value.is_infinite() || (value.is_zero() && m != 0);
447            return Some((sign(value), j, range));
448        }
449        // `0x` with no digit is the number 0 and the `x` is not part of it.
450        return Some((sign(F::from_parts(0, 0)), i + 1, false));
451    }
452
453    let start = i;
454    let mut nonzero = false;
455    let mut digits = 0;
456    while at(i).is_ascii_digit() {
457        nonzero |= at(i) != b'0';
458        digits += 1;
459        i += 1;
460    }
461    if at(i) == b'.' {
462        i += 1;
463        while at(i).is_ascii_digit() {
464            nonzero |= at(i) != b'0';
465            digits += 1;
466            i += 1;
467        }
468    }
469    if digits == 0 {
470        return None;
471    }
472    if matches!(at(i), b'e' | b'E') {
473        let mut k = i + 1;
474        k += usize::from(matches!(at(k), b'+' | b'-'));
475        if at(k).is_ascii_digit() {
476            while at(k).is_ascii_digit() {
477                k += 1;
478            }
479            i = k;
480        }
481    }
482    let text = std::str::from_utf8(&b[start..i]).ok()?;
483    let value: F = text.parse().ok()?;
484    let range = value.is_infinite() || (value.is_zero() && nonzero);
485    Some((sign(value), i, range))
486}
487
488fn float_in<F: Float>(s: &str, type_name: &str) -> Result<F, TypeError> {
489    let b = s.as_bytes();
490    let mut num = 0;
491    while num < b.len() && is_space(b[num]) {
492        num += 1;
493    }
494    let syntax = || TypeError::syntax(type_name, s);
495    let Some((value, len, range)) = strtod::<F>(&b[num..]) else { return Err(syntax()) };
496    if range {
497        return Err(TypeError::new(
498            SqlState::NUMERIC_VALUE_OUT_OF_RANGE,
499            format!("\"{}\" is out of range for type {type_name}", &s[num..num + len]),
500        ));
501    }
502    let mut end = num + len;
503    while end < b.len() && is_space(b[end]) {
504        end += 1;
505    }
506    if end != b.len() {
507        return Err(syntax());
508    }
509    Ok(value)
510}
511
512/// The text input of `float8`.
513pub fn float8_in(s: &str) -> Result<f64, TypeError> {
514    float_in(s, "double precision")
515}
516
517/// The text input of `float4`.
518pub fn float4_in(s: &str) -> Result<f32, TypeError> {
519    float_in(s, "real")
520}
521
522#[cfg(test)]
523mod tests {
524    use super::*;
525
526    fn out8(v: f64, digits: i32) -> String {
527        let mut out = Vec::new();
528        float8_out(v, digits, &mut out);
529        String::from_utf8(out).unwrap()
530    }
531
532    fn out4(v: f32, digits: i32) -> String {
533        let mut out = Vec::new();
534        float4_out(v, digits, &mut out);
535        String::from_utf8(out).unwrap()
536    }
537
538    #[test]
539    fn the_shortest_text_has_the_layout_of_ryu_in_postgres() {
540        for (v, text) in [
541            (1.0, "1"),
542            (-1.5, "-1.5"),
543            (0.1, "0.1"),
544            (1e-4, "0.0001"),
545            (1e-5, "1e-05"),
546            (1.5e-5, "1.5e-05"),
547            (1e14, "100000000000000"),
548            (1e15, "1e+15"),
549            (123456789012345680.0, "1.2345678901234568e+17"),
550            (1e100, "1e+100"),
551            (5e-324, "5e-324"),
552            (f64::MAX, "1.7976931348623157e+308"),
553            (0.0, "0"),
554            (-0.0, "-0"),
555            (f64::NAN, "NaN"),
556            (f64::INFINITY, "Infinity"),
557            (f64::NEG_INFINITY, "-Infinity"),
558        ] {
559            assert_eq!(out8(v, 1), text, "{v:e}");
560        }
561        for (v, text) in [
562            (1.0f32, "1"),
563            (0.1, "0.1"),
564            (123456.0, "123456"),
565            (1234567.0, "1.234567e+06"),
566            (1e-5, "1e-05"),
567            (f32::MAX, "3.4028235e+38"),
568            (-0.0, "-0"),
569        ] {
570            assert_eq!(out4(v, 1), text, "{v:e}");
571        }
572    }
573
574    /// A shorter text that is exactly halfway to the next value reads back to the value, but Ryu in PostgreSQL does not take it. The values are from the oracle.
575    #[test]
576    fn the_shortest_text_is_not_halfway() {
577        for (v, text) in [
578            (1e23, "9.999999999999999e+22"),
579            (9.5e21, "9.500000000000001e+21"),
580            (2e22, "2e+22"),
581            (18014398509481992.0, "1.8014398509481992e+16"),
582            (9007199254740996.0, "9.007199254740996e+15"),
583        ] {
584            assert_eq!(out8(v, 1), text, "{v:e}");
585        }
586        for (v, text) in [
587            (42483247.0f32, "4.2483248e+07"),
588            (84966494.0, "8.4966496e+07"),
589            (16777218.0, "1.6777218e+07"),
590            (33554436.0, "3.3554436e+07"),
591            (16777216.0, "1.6777216e+07"),
592            (33554432.0, "3.3554432e+07"),
593            (1e10, "1e+10"),
594            (1.1754944e-38, "1.1754944e-38"),
595            (-12_890_453.0 / 4.0, "-3.2226132e+06"),
596            (-4_179_433.0 / 8.0, "-522429.12"),
597            (-529_305.0 / 16.0, "-33081.562"),
598        ] {
599            assert_eq!(out4(v, 2), text, "{v:e}");
600        }
601    }
602
603    #[test]
604    fn fewer_digits_use_the_g_format() {
605        assert_eq!(out8(0.1, 0), "0.1");
606        assert_eq!(out8(1.0 / 3.0, 0), "0.333333333333333");
607        assert_eq!(out8(1e15, 0), "1e+15");
608        assert_eq!(out8(123456789012345.0, 0), "123456789012345");
609        assert_eq!(out8(1e-5, 0), "1e-05");
610        assert_eq!(out8(2.5, -14), "2");
611        assert_eq!(out8(-1234.5, -15), "-1e+03");
612        assert_eq!(out8(-0.0, 0), "-0");
613        assert_eq!(out4(0.1, 0), "0.1");
614        assert_eq!(out4(1.0 / 3.0, 0), "0.333333");
615        assert_eq!(out4(1234567.0, 0), "1.23457e+06");
616    }
617
618    #[test]
619    fn the_input_takes_what_strtod_takes() {
620        for (input, value) in [
621            ("1", 1.0),
622            (" -1.5e3 ", -1500.0),
623            (".5", 0.5),
624            ("5.", 5.0),
625            ("1e", f64::NAN),
626            ("0x1p3", 8.0),
627            ("0x.8", 0.5),
628            ("-0X1.8P-1", -0.75),
629            ("0x1.fffffffffffff8p1023", f64::NAN),
630            ("Infinity", f64::INFINITY),
631            ("-inf", f64::NEG_INFINITY),
632            ("+INFINITY", f64::INFINITY),
633            ("1e-310", 1e-310),
634            ("0e999999", 0.0),
635        ] {
636            let got = float8_in(input);
637            if value.is_nan() {
638                assert!(got.is_err(), "{input:?}");
639            } else {
640                assert_eq!(got, Ok(value), "{input:?}");
641            }
642        }
643        assert!(float8_in("nan").unwrap().is_nan());
644        assert!(float8_in("NaN(123)").unwrap().is_nan());
645        assert_eq!(float8_in("0x1.fffffffffffffp1023"), Ok(f64::MAX));
646        assert_eq!(float8_in("0x1p-1074"), Ok(5e-324));
647        assert_eq!(float8_in("0x1.8p-1074"), Ok(1e-323));
648        for input in ["", " ", "x", "1.5x", "infinit", "nan(", "--1", "1e5e"] {
649            let error = float8_in(input).unwrap_err();
650            assert_eq!(error.sqlstate.as_str(), "22P02", "{input:?}");
651            assert_eq!(
652                error.message,
653                format!("invalid input syntax for type double precision: \"{input}\"")
654            );
655        }
656        for (input, number) in
657            [(" 1e400 ", "1e400"), ("-1e-400", "-1e-400"), ("0x1p1024", "0x1p1024")]
658        {
659            let error = float8_in(input).unwrap_err();
660            assert_eq!(error.sqlstate.as_str(), "22003");
661            assert_eq!(
662                error.message,
663                format!("\"{number}\" is out of range for type double precision")
664            );
665        }
666        assert_eq!(
667            float4_in("1e39").unwrap_err().message,
668            "\"1e39\" is out of range for type real"
669        );
670        assert_eq!(float4_in("1e-40"), Ok(1e-40));
671        assert_eq!(float4_in("3.4028235e38"), Ok(f32::MAX));
672        assert_eq!(
673            float4_in("x").unwrap_err().message,
674            "invalid input syntax for type real: \"x\""
675        );
676    }
677}