Skip to main content

firebird_wire/
decfloat.rs

1//! Decodificação de `DECFLOAT(16)`/`DECFLOAT(34)` do Firebird — os formatos de
2//! ponto flutuante decimal IEEE 754-2008 *decimal64* e *decimal128*, com o
3//! coeficiente em **DPD** (Densely Packed Decimal).
4//!
5//! O valor é `(-1)^sinal · coeficiente · 10^expoente`. O campo de combinação de
6//! 5 bits codifica o dígito mais significativo e os 2 bits altos do expoente
7//! enviesado; o restante são a continuação do expoente e os *declets* DPD (cada
8//! declet = 10 bits → 3 dígitos decimais). Ver IEEE 754-2008 §3.5 e a
9//! especificação de aritmética decimal de Mike Cowlishaw.
10
11use std::fmt;
12
13/// Um valor `DECFLOAT` decodificado.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct DecFloat {
16    negative: bool,
17    kind: DecKind,
18    /// Coeficiente (significando) como inteiro; o valor é `coefficient · 10^exponent`.
19    coefficient: u128,
20    exponent: i32,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24enum DecKind {
25    Finite,
26    Infinity,
27    /// NaN silencioso (quiet) ou sinalizador (signaling) — não distinguimos aqui.
28    NaN,
29}
30
31impl DecFloat {
32    /// Decodifica um `DECFLOAT(16)` (decimal64, 8 bytes) na ordem em que o
33    /// Firebird o transmite (big-endian, como o INT128).
34    pub fn from_decimal64(bytes: [u8; 8]) -> DecFloat {
35        decode(u64::from_be_bytes(bytes) as u128, 64)
36    }
37
38    /// Decodifica um `DECFLOAT(34)` (decimal128, 16 bytes), big-endian.
39    pub fn from_decimal128(bytes: [u8; 16]) -> DecFloat {
40        decode(u128::from_be_bytes(bytes), 128)
41    }
42
43    /// Se é negativo (inclui `-0` e `-Infinity`).
44    pub fn is_negative(&self) -> bool {
45        self.negative
46    }
47
48    /// Se é um valor finito (não Infinity nem NaN).
49    pub fn is_finite(&self) -> bool {
50        self.kind == DecKind::Finite
51    }
52
53    /// Se é `NaN` (not a number).
54    pub fn is_nan(&self) -> bool {
55        self.kind == DecKind::NaN
56    }
57
58    /// Se é `Infinity` ou `-Infinity`.
59    pub fn is_infinite(&self) -> bool {
60        self.kind == DecKind::Infinity
61    }
62
63    /// O coeficiente e o expoente de base 10 (`valor = ±coefficient·10^exponent`),
64    /// para valores finitos.
65    pub fn to_parts(&self) -> Option<(bool, u128, i32)> {
66        self.is_finite()
67            .then_some((self.negative, self.coefficient, self.exponent))
68    }
69
70    /// Constrói um valor finito `(-1)^negative · coefficient · 10^exponent`.
71    pub fn from_parts(negative: bool, coefficient: u128, exponent: i32) -> DecFloat {
72        DecFloat {
73            negative,
74            kind: DecKind::Finite,
75            coefficient,
76            exponent,
77        }
78    }
79
80    /// `±Infinity`.
81    pub fn infinity(negative: bool) -> DecFloat {
82        DecFloat {
83            negative,
84            kind: DecKind::Infinity,
85            coefficient: 0,
86            exponent: 0,
87        }
88    }
89
90    /// `NaN` (quiet).
91    pub fn nan() -> DecFloat {
92        DecFloat {
93            negative: false,
94            kind: DecKind::NaN,
95            coefficient: 0,
96            exponent: 0,
97        }
98    }
99
100    /// Codifica como `DECFLOAT(16)` (decimal64, 8 bytes big-endian, como o
101    /// Firebird espera). `None` se o valor não couber em decimal64 (mais de 16
102    /// dígitos significativos ou expoente fora de faixa).
103    pub fn to_decimal64(&self) -> Option<[u8; 8]> {
104        Some((self.encode_bits(64)? as u64).to_be_bytes())
105    }
106
107    /// Codifica como `DECFLOAT(34)` (decimal128, 16 bytes big-endian).
108    pub fn to_decimal128(&self) -> Option<[u8; 16]> {
109        Some(self.encode_bits(128)?.to_be_bytes())
110    }
111
112    fn encode_bits(&self, width: u32) -> Option<u128> {
113        match self.kind {
114            DecKind::Finite => encode(self.negative, self.coefficient, self.exponent, width),
115            DecKind::Infinity => Some(special_bits(self.negative, 0b1_1110, width)),
116            DecKind::NaN => Some(special_bits(self.negative, 0b1_1111, width)),
117        }
118    }
119}
120
121/// Erro ao analisar uma string em [`DecFloat`].
122#[derive(Debug, Clone, PartialEq, Eq)]
123pub struct ParseDecFloatError;
124
125impl fmt::Display for ParseDecFloatError {
126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        f.write_str("string DECFLOAT inválida")
128    }
129}
130
131impl std::error::Error for ParseDecFloatError {}
132
133impl std::str::FromStr for DecFloat {
134    type Err = ParseDecFloatError;
135
136    /// Analisa um decimal como `-123.45`, `1.5E-3`, `Infinity`/`-Inf`/`NaN`.
137    /// Preserva os dígitos exatos (os zeros à direita são significativos).
138    fn from_str(s: &str) -> Result<DecFloat, ParseDecFloatError> {
139        let t = s.trim();
140        let (negative, body) = match t.strip_prefix('-') {
141            Some(rest) => (true, rest),
142            None => (false, t.strip_prefix('+').unwrap_or(t)),
143        };
144        match body.to_ascii_lowercase().as_str() {
145            "inf" | "infinity" => return Ok(DecFloat::infinity(negative)),
146            "nan" => return Ok(DecFloat::nan()),
147            "" => return Err(ParseDecFloatError),
148            _ => {}
149        }
150        // Separa a mantissa do expoente científico opcional.
151        let (mantissa, exp_part) = match body.split_once(['e', 'E']) {
152            Some((m, e)) => (m, e.parse::<i32>().map_err(|_| ParseDecFloatError)?),
153            None => (body, 0),
154        };
155        let (int_part, frac_part) = match mantissa.split_once('.') {
156            Some((i, f)) => (i, f),
157            None => (mantissa, ""),
158        };
159        if int_part.is_empty() && frac_part.is_empty() {
160            return Err(ParseDecFloatError);
161        }
162        let mut digits = String::with_capacity(int_part.len() + frac_part.len());
163        digits.push_str(int_part);
164        digits.push_str(frac_part);
165        if digits.is_empty() || !digits.bytes().all(|b| b.is_ascii_digit()) {
166            return Err(ParseDecFloatError);
167        }
168        let coefficient: u128 = digits.parse().map_err(|_| ParseDecFloatError)?;
169        let exponent = exp_part - frac_part.len() as i32;
170        Ok(DecFloat::from_parts(negative, coefficient, exponent))
171    }
172}
173
174/// Bits de um valor especial (Infinity/NaN): só o campo de combinação e o sinal.
175fn special_bits(negative: bool, combo: u128, width: u32) -> u128 {
176    ((negative as u128) << (width - 1)) | (combo << (width - 6))
177}
178
179/// Inverso de [`decode`]: monta os bits decimal64/decimal128 de um valor finito.
180/// `None` se o coeficiente tiver dígitos demais ou o expoente sair da faixa.
181fn encode(negative: bool, coefficient: u128, exponent: i32, width: u32) -> Option<u128> {
182    let (ecbits, declets, bias) = match width {
183        64 => (8u32, 5u32, 398i32),
184        _ => (12u32, 11u32, 6176i32), // 128
185    };
186    let total_digits = (3 * declets + 1) as usize;
187    let digits = coefficient.to_string();
188    if digits.len() > total_digits {
189        return None; // coeficiente não cabe nesta largura
190    }
191    let biased = exponent.checked_add(bias)?;
192    let max_biased = (1i32 << (ecbits + 2)) - 1;
193    if !(0..=max_biased).contains(&biased) {
194        return None; // expoente fora de faixa
195    }
196    let biased = biased as u32;
197
198    // Dígitos com zeros à esquerda até a largura total (MSD + declets×3).
199    let mut padded = vec![0u8; total_digits - digits.len()];
200    padded.extend(digits.bytes().map(|b| b - b'0'));
201
202    let msd = padded[0] as u32;
203    let exp_top2 = (biased >> ecbits) & 0b11;
204    let econ = biased & ((1 << ecbits) - 1);
205    let combo = if msd <= 7 {
206        (exp_top2 << 3) | msd
207    } else {
208        0b1_1000 | (exp_top2 << 1) | (msd & 1)
209    };
210
211    let mut bits: u128 = 0;
212    bits |= (negative as u128) << (width - 1);
213    bits |= (combo as u128) << (width - 6);
214    bits |= (econ as u128) << (width - 6 - ecbits);
215    for i in 0..declets as usize {
216        let g = &padded[1 + i * 3..1 + i * 3 + 3];
217        let dpd = bcd_to_dpd(g[0] as u16, g[1] as u16, g[2] as u16) as u128;
218        let bit = ((declets as usize - 1 - i) * 10) as u32;
219        bits |= dpd << bit;
220    }
221    Some(bits)
222}
223
224impl fmt::Display for DecFloat {
225    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226        match self.kind {
227            DecKind::Infinity => f.write_str(if self.negative {
228                "-Infinity"
229            } else {
230                "Infinity"
231            }),
232            DecKind::NaN => f.write_str("NaN"),
233            DecKind::Finite => {
234                if self.negative {
235                    f.write_str("-")?;
236                }
237                f.write_str(&render_finite(self.coefficient, self.exponent))
238            }
239        }
240    }
241}
242
243/// Renderiza `coefficient · 10^exponent` como uma string decimal simples,
244/// preservando os zeros à direita significativos (o DECFLOAT os mantém).
245fn render_finite(coefficient: u128, exponent: i32) -> String {
246    let digits = coefficient.to_string();
247    if exponent >= 0 {
248        // Inteiro: acrescenta `exponent` zeros.
249        let mut s = digits;
250        s.extend(std::iter::repeat_n('0', exponent as usize));
251        s
252    } else {
253        let frac = (-exponent) as usize;
254        if digits.len() > frac {
255            // Insere o ponto a `frac` casas a partir da direita.
256            let point = digits.len() - frac;
257            format!("{}.{}", &digits[..point], &digits[point..])
258        } else {
259            // 0.000… com zeros à esquerda para completar a parte fracionária.
260            let zeros = frac - digits.len();
261            format!("0.{}{}", "0".repeat(zeros), digits)
262        }
263    }
264}
265
266/// Decodifica o campo de bits de 64 ou 128 bits (MSB = sinal) em [`DecFloat`].
267fn decode(bits: u128, width: u32) -> DecFloat {
268    let (ecbits, declets, bias) = match width {
269        64 => (8u32, 5u32, 398i32),
270        _ => (12u32, 11u32, 6176i32), // 128
271    };
272    let negative = (bits >> (width - 1)) & 1 == 1;
273    let combo = ((bits >> (width - 6)) & 0x1F) as u32;
274
275    // Campo de combinação: dígito mais significativo + 2 bits altos do expoente,
276    // ou marcador de Infinity/NaN.
277    let (msd, exp_top2) = if combo >> 3 == 0b11 {
278        if (combo >> 1) & 0b1111 == 0b1111 {
279            // 11110 = Infinity, 11111 = NaN.
280            let kind = if combo & 1 == 0 {
281                DecKind::Infinity
282            } else {
283                DecKind::NaN
284            };
285            return DecFloat {
286                negative,
287                kind,
288                coefficient: 0,
289                exponent: 0,
290            };
291        }
292        // MSD é 8 ou 9; os 2 bits altos do expoente são (combo>>1)&0b11.
293        (8 + (combo & 1), (combo >> 1) & 0b11)
294    } else {
295        // MSD é 0..=7 (bits baixos); os 2 bits altos do expoente são (combo>>3)&0b11.
296        (combo & 0b111, (combo >> 3) & 0b11)
297    };
298
299    let econ = ((bits >> (width - 6 - ecbits)) & ((1u128 << ecbits) - 1)) as u32;
300    let biased_exp = ((exp_top2 << ecbits) | econ) as i32;
301    let exponent = biased_exp - bias;
302
303    // Coeficiente: o MSD seguido de cada declet (3 dígitos) decodificado de DPD.
304    let coef_bits = width - 6 - ecbits; // = declets * 10
305    let mut coefficient = msd as u128;
306    for d in (0..declets).rev() {
307        let dpd = ((bits >> (d * 10)) & 0x3FF) as u16;
308        debug_assert!((d * 10) < coef_bits);
309        coefficient = coefficient * 1000 + dpd_to_int(dpd) as u128;
310    }
311
312    DecFloat {
313        negative,
314        kind: DecKind::Finite,
315        coefficient,
316        exponent,
317    }
318}
319
320/// Decodifica um declet DPD de 10 bits nos seus três dígitos decimais (0..=999),
321/// via a tabela construída a partir do codificador canônico BCD→DPD.
322fn dpd_to_int(dpd: u16) -> u16 {
323    DPD_DECODE[dpd as usize & 0x3FF]
324}
325
326/// Tabela de decodificação DPD (1024 entradas), construída uma vez invertendo o
327/// codificador [`bcd_to_dpd`]. Códigos não canônicos ficam em 0 (o Firebird só
328/// emite formas canônicas).
329static DPD_DECODE: std::sync::LazyLock<[u16; 1024]> = std::sync::LazyLock::new(|| {
330    let mut table = [0u16; 1024];
331    for n in 0u16..1000 {
332        let (d2, d1, d0) = (n / 100, (n / 10) % 10, n % 10);
333        table[bcd_to_dpd(d2, d1, d0) as usize] = n;
334    }
335    table
336});
337
338/// Codifica três dígitos decimais (`d2` mais significativo) em um declet DPD de
339/// 10 bits, conforme a tabela canônica de codificação (IEEE 754-2008 / Cowlishaw).
340fn bcd_to_dpd(d2: u16, d1: u16, d0: u16) -> u16 {
341    // Bits BCD de cada dígito (8s, 4s, 2s, 1s).
342    let (a, b, c, dd) = ((d2 >> 3) & 1, (d2 >> 2) & 1, (d2 >> 1) & 1, d2 & 1);
343    let (e, f, g, h) = ((d1 >> 3) & 1, (d1 >> 2) & 1, (d1 >> 1) & 1, d1 & 1);
344    let (i, j, k, m) = ((d0 >> 3) & 1, (d0 >> 2) & 1, (d0 >> 1) & 1, d0 & 1);
345    let aei = (a << 2) | (e << 1) | i;
346    // Saída como (b9 b8 b7 | b6 b5 b4 | b3 | b2 b1 b0).
347    let (p, q, r, s, t, u, v, x, y, z) = match aei {
348        0b000 => (b, c, dd, f, g, h, 0, j, k, m),
349        0b001 => (b, c, dd, f, g, h, 1, 0, 0, m),
350        0b010 => (b, c, dd, j, k, h, 1, 0, 1, m),
351        0b011 => (b, c, dd, 1, 0, h, 1, 1, 1, m),
352        0b100 => (j, k, dd, f, g, h, 1, 1, 0, m),
353        0b101 => (f, g, dd, 0, 1, h, 1, 1, 1, m),
354        0b110 => (j, k, dd, 0, 0, h, 1, 1, 1, m),
355        _ => (0, 0, dd, 1, 1, h, 1, 1, 1, m), // 0b111
356    };
357    (p << 9)
358        | (q << 8)
359        | (r << 7)
360        | (s << 6)
361        | (t << 5)
362        | (u << 4)
363        | (v << 3)
364        | (x << 2)
365        | (y << 1)
366        | z
367}
368
369#[cfg(test)]
370mod tests {
371    use super::*;
372
373    #[test]
374    fn dpd_roundtrip_all_declets() {
375        // Todo dígito 0..=999 codifica e decodifica de volta a si mesmo.
376        for n in 0u16..1000 {
377            let (d2, d1, d0) = (n / 100, (n / 10) % 10, n % 10);
378            let dpd = bcd_to_dpd(d2, d1, d0);
379            assert_eq!(dpd_to_int(dpd), n, "falhou para {n}");
380        }
381    }
382
383    #[test]
384    fn dpd_known_anchors() {
385        // Vetores canônicos conhecidos da especificação DPD.
386        assert_eq!(bcd_to_dpd(0, 0, 5), 0b00_0000_0101); // 005 -> 0x005
387        assert_eq!(bcd_to_dpd(0, 0, 9), 0b00_0000_1001); // 009 -> 0x009
388        // 765: dígitos < 8 viram a concatenação direta dos 3 bits baixos:
389        // (111)(110)(0)(101) = 0b11_1110_0101.
390        assert_eq!(bcd_to_dpd(7, 6, 5), 0b11_1110_0101);
391        assert_eq!(bcd_to_dpd(9, 9, 9), 0b00_1111_1111); // 999 -> 0x0FF
392    }
393
394    #[test]
395    fn render_places_decimal_point() {
396        assert_eq!(render_finite(12345, -2), "123.45");
397        assert_eq!(render_finite(100, -2), "1.00");
398        assert_eq!(render_finite(5, -4), "0.0005");
399        assert_eq!(render_finite(5, 3), "5000");
400        assert_eq!(render_finite(0, 0), "0");
401    }
402
403    #[test]
404    fn parse_and_roundtrip_encode_decode() {
405        use std::str::FromStr;
406        for s in [
407            "0",
408            "1",
409            "123.45",
410            "-3.14159",
411            "100.00",
412            "0.0005",
413            "5000",
414            "-0.0",
415            "9999999999999999",
416        ] {
417            let d = DecFloat::from_str(s).unwrap();
418            // decimal128 sempre cabe nestes exemplos; relê o mesmo texto.
419            let back = DecFloat::from_decimal128(d.to_decimal128().unwrap());
420            assert_eq!(back.to_string(), d.to_string(), "decimal128 falhou em {s}");
421        }
422        // decimal64 (16 dígitos) com casos que cabem.
423        for s in ["123.45", "-3.14159", "0.0005", "1234567890123456"] {
424            let d = DecFloat::from_str(s).unwrap();
425            let back = DecFloat::from_decimal64(d.to_decimal64().unwrap());
426            assert_eq!(back.to_string(), d.to_string(), "decimal64 falhou em {s}");
427        }
428    }
429
430    #[test]
431    fn parse_scientific_and_specials() {
432        use std::str::FromStr;
433        assert_eq!(DecFloat::from_str("1.5E-3").unwrap().to_string(), "0.0015");
434        assert_eq!(DecFloat::from_str("12E3").unwrap().to_string(), "12000");
435        assert!(DecFloat::from_str("Infinity").unwrap().is_infinite());
436        assert!(DecFloat::from_str("-inf").unwrap().is_infinite());
437        assert!(DecFloat::from_str("NaN").unwrap().is_nan());
438        assert!(DecFloat::from_str("abc").is_err());
439    }
440
441    #[test]
442    fn encode_overflow_returns_none() {
443        // 17 dígitos não cabem em decimal64 (máx 16).
444        let d = DecFloat::from_parts(false, 12_345_678_901_234_567, 0);
445        assert!(d.to_decimal64().is_none());
446        // Mas cabem em decimal128.
447        assert!(d.to_decimal128().is_some());
448    }
449
450    #[test]
451    fn encode_specials_roundtrip() {
452        for d in [
453            DecFloat::infinity(false),
454            DecFloat::infinity(true),
455            DecFloat::nan(),
456        ] {
457            let back = DecFloat::from_decimal128(d.to_decimal128().unwrap());
458            assert_eq!(back.is_infinite(), d.is_infinite());
459            assert_eq!(back.is_nan(), d.is_nan());
460            assert_eq!(back.is_negative(), d.is_negative());
461        }
462    }
463
464    #[test]
465    fn decimal128_one() {
466        // 1 em decimal128 = coeficiente 1, expoente 0 → expoente enviesado = bias.
467        // combo "small": exp_top2 ocupa os 2 bits altos (3..4), MSD os 3 baixos.
468        let bias = 6176u32; // 0x1820
469        let exp_top2 = (bias >> 12) as u128; // 1
470        let econ = (bias & 0xFFF) as u128; // 2080
471        let combo = exp_top2 << 3; // MSD 0 nos 3 bits baixos
472        let last_declet = bcd_to_dpd(0, 0, 1) as u128; // dígito 001
473        let bits = (combo << (128 - 6)) | (econ << (128 - 6 - 12)) | last_declet;
474        let d = decode(bits, 128);
475        assert!(d.is_finite() && !d.is_negative());
476        assert_eq!(d.to_parts(), Some((false, 1, 0)));
477        assert_eq!(d.to_string(), "1");
478    }
479}