tdb_succinct/tfc/
decimal.rs

1use super::integer::{bigint_to_storage, storage_to_bigint_and_sign, NEGATIVE_ZERO};
2use bytes::Buf;
3use lazy_static::lazy_static;
4use regex::Regex;
5use rug::Integer;
6use thiserror::Error;
7
8#[derive(PartialEq, Debug)]
9pub struct Decimal(pub(crate) String);
10
11#[derive(Debug, Error)]
12#[error("Invalid format for decimal: `{value}`")]
13pub struct DecimalValidationError {
14    pub value: String,
15}
16
17impl Decimal {
18    pub fn new(s: String) -> Result<Self, DecimalValidationError> {
19        validate_decimal(&s)?;
20        Ok(Decimal(s))
21    }
22}
23
24pub fn validate_decimal(s: &str) -> Result<(), DecimalValidationError> {
25    lazy_static! {
26        static ref RE: Regex = Regex::new(r"^-?\d+(\.\d+)?([eE@](-|\+)?\d+)?$").unwrap();
27    }
28    if RE.is_match(s) {
29        Ok(())
30    } else {
31        Err(DecimalValidationError {
32            value: s.to_string(),
33        })
34    }
35}
36
37fn encode_fraction(fraction: Option<&str>) -> Vec<u8> {
38    if let Some(f) = fraction {
39        if f.is_empty() {
40            return vec![0x00]; // a "false zero" so we don't represent it at all.
41        }
42        let len = f.len();
43        let size = len / 2 + usize::from(len % 2 != 0);
44        let mut bcd = Vec::with_capacity(size);
45        for i in 0..size {
46            let last = if i * 2 + 2 > len {
47                i * 2 + 1
48            } else {
49                i * 2 + 2
50            };
51            let two = &f[2 * i..last];
52            let mut this_int = centary_decimal_encode(two);
53            this_int <<= 1;
54            if i != size - 1 {
55                this_int |= 1 // add continuation bit.
56            }
57            bcd.push(this_int)
58        }
59        bcd
60    } else {
61        vec![0x00] // a "false zero" so we don't represent no fraction as a fraction
62    }
63}
64
65fn centary_decimal_encode(s: &str) -> u8 {
66    if s.len() == 1 {
67        let i = s.parse::<u8>().unwrap();
68        i * 11 + 1
69    } else {
70        let i = s.parse::<u8>().unwrap();
71        let o = i / 10 + 1;
72        i + o + 1
73    }
74}
75
76fn centary_decimal_decode(i: u8) -> String {
77    let j = i - 1;
78    if j % 11 == 0 {
79        let num = j / 11;
80        format!("{num:}")
81    } else {
82        let d = j / 11;
83        let num = j - d - 1;
84        format!("{num:02}")
85    }
86}
87
88pub fn decode_fraction<B: Buf>(fraction_buf: &mut B, is_pos: bool) -> String {
89    let mut first_byte = fraction_buf.chunk()[0];
90    if !is_pos {
91        first_byte = !first_byte;
92    }
93    if first_byte == 0x00 {
94        "".to_string()
95    } else {
96        let mut s = String::new();
97        while fraction_buf.has_remaining() {
98            let mut byte = fraction_buf.get_u8();
99            if !is_pos {
100                byte = !byte;
101            }
102            let num = byte >> 1;
103            let res = centary_decimal_decode(num);
104            s.push_str(&res);
105            if res.len() == 1 || byte & 1 == 0 {
106                break;
107            }
108        }
109        s
110    }
111}
112
113pub fn decimal_to_storage(decimal: &str) -> Vec<u8> {
114    lazy_static! {
115        static ref STD: Regex = Regex::new(r"^-?\d+(\.\d*)?$").unwrap();
116        static ref SCIENTIFIC: Regex = Regex::new(
117            r"^(?P<sign>-)?(?P<integer>\d+)(\.(?P<fraction>\d+))?([eE@](?P<exp>(-|\+)?\d+))?$"
118        )
119        .unwrap();
120    }
121    if STD.is_match(decimal) {
122        let mut parts = decimal.split('.');
123        let bigint = parts.next().unwrap_or(decimal);
124        let fraction = parts.next();
125        let integer_part = bigint.parse::<Integer>().unwrap();
126        let is_neg = decimal.starts_with('-');
127        integer_and_fraction_to_storage(is_neg, integer_part, fraction)
128    } else {
129        let captures = SCIENTIFIC.captures(decimal).unwrap(); // prevalidated
130        let is_neg = captures.name("sign").is_some();
131        let exp: i32 = if let Some(exp_string) = captures.name("exp") {
132            exp_string.as_str().parse::<i32>().unwrap()
133        } else {
134            0_i32
135        };
136        let integer_str = captures.name("integer").map(|m| m.as_str()).unwrap();
137        let fraction_str = captures.name("fraction").map_or_else(|| "", |m| m.as_str());
138        let left_pad = if -exp > integer_str.len() as i32 {
139            "0.".to_string()
140                + &"0".repeat((-exp - integer_str.len() as i32).unsigned_abs() as usize)
141        } else {
142            "".to_string()
143        };
144        let right_pad = if exp > fraction_str.len() as i32 {
145            "0".repeat((exp - fraction_str.len() as i32) as usize)
146        } else {
147            "".to_string()
148        };
149        let left_len = left_pad.len() as i32 + integer_str.len() as i32;
150        let combined = left_pad + integer_str + fraction_str + &right_pad;
151        let shift = (left_len + exp) as usize;
152        let integer_str = &combined[0..shift];
153        let sign = if is_neg { -1 } else { 1 };
154        let integer_part = sign
155            * integer_str
156                .parse::<Integer>()
157                .unwrap_or_else(|_| Integer::from(0));
158        let fraction = &combined[shift..];
159        let fraction = if fraction.is_empty() {
160            None
161        } else {
162            Some(fraction)
163        };
164        integer_and_fraction_to_storage(is_neg, integer_part, fraction)
165    }
166}
167
168pub fn storage_to_decimal<B: Buf>(bytes: &mut B) -> String {
169    let (int, is_pos) = storage_to_bigint_and_sign(bytes);
170    let fraction = decode_fraction(bytes, is_pos);
171    if fraction.is_empty() {
172        format!("{int:}")
173    } else {
174        let sign = if int == 0 && !is_pos { "-" } else { "" };
175        format!("{sign:}{int:}.{fraction:}")
176    }
177}
178
179pub fn integer_and_fraction_to_storage(
180    is_neg: bool,
181    integer: Integer,
182    fraction: Option<&str>,
183) -> Vec<u8> {
184    let prefix = bigint_to_storage(integer.clone());
185    let mut prefix = if integer == 0 && is_neg {
186        vec![NEGATIVE_ZERO] // negative zero
187    } else {
188        prefix
189    };
190    let suffix = if is_neg {
191        let mut suffix = encode_fraction(fraction);
192        for elt in &mut suffix {
193            *elt = !(*elt)
194        }
195        suffix
196    } else {
197        encode_fraction(fraction)
198    };
199    prefix.extend(suffix);
200    prefix
201}