zero_postgres/conversion/
numeric_util.rs1use crate::error::{Error, Result};
4
5const NUMERIC_NEG: u16 = 0x4000;
7const NUMERIC_NAN: u16 = 0xC000;
8const NUMERIC_PINF: u16 = 0xD000;
9const NUMERIC_NINF: u16 = 0xF000;
10
11pub fn numeric_to_string(bytes: &[u8]) -> Result<String> {
23 let (header, digit_bytes) = bytes
24 .split_first_chunk::<8>()
25 .ok_or_else(|| Error::Decode(format!("invalid NUMERIC length: {}", bytes.len())))?;
26
27 let ndigits = i16::from_be_bytes([header[0], header[1]]) as usize;
28 let weight = i16::from_be_bytes([header[2], header[3]]) as i32;
29 let sign = u16::from_be_bytes([header[4], header[5]]);
30 let dscale = u16::from_be_bytes([header[6], header[7]]) as i32;
31
32 match sign {
34 NUMERIC_NAN => return Ok("NaN".to_string()),
35 NUMERIC_PINF => return Ok("Infinity".to_string()),
36 NUMERIC_NINF => return Ok("-Infinity".to_string()),
37 _ => {}
38 }
39
40 if ndigits == 0 {
42 return if dscale > 0 {
43 let mut s = "0.".to_string();
44 for _ in 0..dscale {
45 s.push('0');
46 }
47 Ok(s)
48 } else {
49 Ok("0".to_string())
50 };
51 }
52
53 let mut digits = Vec::with_capacity(ndigits);
55 let mut remaining = digit_bytes;
56 for _ in 0..ndigits {
57 let Some((pair, rest)) = remaining.split_first_chunk::<2>() else {
58 return Err(Error::Decode(format!(
59 "truncated NUMERIC data: {} bytes",
60 bytes.len()
61 )));
62 };
63 remaining = rest;
64 digits.push(i16::from_be_bytes(*pair));
65 }
66
67 let mut result = String::new();
69
70 if sign == NUMERIC_NEG {
72 result.push('-');
73 }
74
75 let int_digits = (weight + 1) * 4;
78
79 if int_digits <= 0 {
80 result.push_str("0.");
82 for _ in 0..(-int_digits) {
84 result.push('0');
85 }
86 let mut frac_digits_written = -int_digits;
88 for (i, &d) in digits.iter().enumerate() {
89 let s = format!("{:04}", d);
90 if i == ndigits - 1 && dscale > 0 {
91 for c in s.chars() {
93 if frac_digits_written < dscale {
94 result.push(c);
95 frac_digits_written += 1;
96 }
97 }
98 } else {
99 result.push_str(&s);
100 frac_digits_written += 4;
101 }
102 }
103 while frac_digits_written < dscale {
105 result.push('0');
106 frac_digits_written += 1;
107 }
108 } else {
109 let mut d_idx = 0;
111
112 if d_idx < ndigits {
114 let d = digits[d_idx];
115 result.push_str(&d.to_string());
116 d_idx += 1;
117 }
118
119 let full_int_groups = weight as usize;
121 while d_idx <= full_int_groups && d_idx < ndigits {
122 result.push_str(&format!("{:04}", digits[d_idx]));
123 d_idx += 1;
124 }
125
126 while d_idx <= full_int_groups {
128 result.push_str("0000");
129 d_idx += 1;
130 }
131
132 if dscale > 0 {
134 result.push('.');
135
136 let mut frac_digits_written = 0;
137 while d_idx < ndigits && frac_digits_written < dscale {
138 let s = format!("{:04}", digits[d_idx]);
139 for c in s.chars() {
140 if frac_digits_written < dscale {
141 result.push(c);
142 frac_digits_written += 1;
143 }
144 }
145 d_idx += 1;
146 }
147
148 while frac_digits_written < dscale {
150 result.push('0');
151 frac_digits_written += 1;
152 }
153 }
154 }
155
156 Ok(result)
157}
158
159pub fn numeric_to_f64(bytes: &[u8]) -> Result<f64> {
161 let (header, digit_bytes) = bytes
162 .split_first_chunk::<8>()
163 .ok_or_else(|| Error::Decode(format!("NUMERIC too short: {} bytes", bytes.len())))?;
164
165 let ndigits = i16::from_be_bytes([header[0], header[1]]) as usize;
166 let weight = i16::from_be_bytes([header[2], header[3]]);
167 let sign = u16::from_be_bytes([header[4], header[5]]);
168
169 match sign {
171 NUMERIC_NAN => return Ok(f64::NAN),
172 NUMERIC_PINF => return Ok(f64::INFINITY),
173 NUMERIC_NINF => return Ok(f64::NEG_INFINITY),
174 _ => {}
175 }
176
177 if ndigits == 0 {
179 return Ok(0.0);
180 }
181
182 let mut result: f64 = 0.0;
185 let mut remaining = digit_bytes;
186 for i in 0..ndigits {
187 let Some((pair, rest)) = remaining.split_first_chunk::<2>() else {
188 return Err(Error::Decode(format!(
189 "truncated NUMERIC data: {} bytes",
190 bytes.len()
191 )));
192 };
193 remaining = rest;
194 let digit = i16::from_be_bytes(*pair) as f64;
195 let power = (weight as i32) - (i as i32);
197 result += digit * 10000_f64.powi(power);
198 }
199
200 if sign == NUMERIC_NEG {
202 result = -result;
203 }
204
205 if result.is_infinite() && sign != NUMERIC_PINF && sign != NUMERIC_NINF {
207 return Err(Error::Decode("NUMERIC value overflows f64".to_string()));
208 }
209
210 Ok(result)
211}
212
213pub fn numeric_to_f32(bytes: &[u8]) -> Result<f32> {
215 let (header, digit_bytes) = bytes
216 .split_first_chunk::<8>()
217 .ok_or_else(|| Error::Decode(format!("NUMERIC too short: {} bytes", bytes.len())))?;
218
219 let ndigits = i16::from_be_bytes([header[0], header[1]]) as usize;
220 let weight = i16::from_be_bytes([header[2], header[3]]);
221 let sign = u16::from_be_bytes([header[4], header[5]]);
222
223 match sign {
225 NUMERIC_NAN => return Ok(f32::NAN),
226 NUMERIC_PINF => return Ok(f32::INFINITY),
227 NUMERIC_NINF => return Ok(f32::NEG_INFINITY),
228 _ => {}
229 }
230
231 if ndigits == 0 {
233 return Ok(0.0);
234 }
235
236 let mut result: f64 = 0.0;
238 let mut remaining = digit_bytes;
239 for i in 0..ndigits {
240 let Some((pair, rest)) = remaining.split_first_chunk::<2>() else {
241 return Err(Error::Decode(format!(
242 "truncated NUMERIC data: {} bytes",
243 bytes.len()
244 )));
245 };
246 remaining = rest;
247 let digit = i16::from_be_bytes(*pair) as f64;
248 let power = (weight as i32) - (i as i32);
249 result += digit * 10000_f64.powi(power);
250 }
251
252 if sign == NUMERIC_NEG {
254 result = -result;
255 }
256
257 if result > f32::MAX as f64 || result < f32::MIN as f64 {
259 return Err(Error::Decode("NUMERIC value overflows f32".to_string()));
260 }
261
262 let result_f32 = result as f32;
263
264 if result_f32.is_infinite() && result.is_finite() {
266 return Err(Error::Decode("NUMERIC value overflows f32".to_string()));
267 }
268
269 Ok(result_f32)
270}
271
272#[cfg(test)]
273mod tests {
274 use super::*;
275
276 fn make_numeric(ndigits: i16, weight: i16, sign: u16, dscale: u16, digits: &[i16]) -> Vec<u8> {
278 let mut buf = Vec::new();
279 buf.extend_from_slice(&ndigits.to_be_bytes());
280 buf.extend_from_slice(&weight.to_be_bytes());
281 buf.extend_from_slice(&sign.to_be_bytes());
282 buf.extend_from_slice(&dscale.to_be_bytes());
283 for &d in digits {
284 buf.extend_from_slice(&d.to_be_bytes());
285 }
286 buf
287 }
288
289 #[test]
290 fn numeric_to_string_zero() {
291 let bytes1 = make_numeric(0, 0, 0x0000, 0, &[]);
292 assert_eq!(numeric_to_string(&bytes1).unwrap(), "0");
293
294 let bytes2 = make_numeric(0, 0, 0x0000, 2, &[]);
296 assert_eq!(numeric_to_string(&bytes2).unwrap(), "0.00");
297 }
298
299 #[test]
300 fn numeric_to_string_simple() {
301 let bytes = make_numeric(2, 1, 0x0000, 0, &[1, 2345]);
303 assert_eq!(numeric_to_string(&bytes).unwrap(), "12345");
304 }
305
306 #[test]
307 fn numeric_to_string_decimal() {
308 let bytes = make_numeric(2, 0, 0x0000, 2, &[123, 4500]);
310 assert_eq!(numeric_to_string(&bytes).unwrap(), "123.45");
311 }
312
313 #[test]
314 fn numeric_to_string_negative() {
315 let bytes = make_numeric(2, 0, 0x4000, 2, &[123, 4500]);
317 assert_eq!(numeric_to_string(&bytes).unwrap(), "-123.45");
318 }
319
320 #[test]
321 fn numeric_to_string_small_decimal() {
322 let bytes = make_numeric(1, -1, 0x0000, 4, &[1]);
324 assert_eq!(numeric_to_string(&bytes).unwrap(), "0.0001");
325 }
326
327 #[test]
328 fn numeric_to_string_special_values() {
329 let bytes1 = make_numeric(0, 0, 0xC000, 0, &[]);
331 assert_eq!(numeric_to_string(&bytes1).unwrap(), "NaN");
332
333 let bytes2 = make_numeric(0, 0, 0xD000, 0, &[]);
335 assert_eq!(numeric_to_string(&bytes2).unwrap(), "Infinity");
336
337 let bytes3 = make_numeric(0, 0, 0xF000, 0, &[]);
339 assert_eq!(numeric_to_string(&bytes3).unwrap(), "-Infinity");
340 }
341
342 #[test]
343 fn numeric_to_f64_basic() {
344 let bytes = make_numeric(2, 0, 0x0000, 2, &[123, 4500]);
346 let result = numeric_to_f64(&bytes).unwrap();
347 assert!((result - 123.45).abs() < 0.001);
348 }
349
350 #[test]
351 fn numeric_to_f64_negative() {
352 let bytes = make_numeric(2, 0, 0x4000, 2, &[123, 4500]);
354 let result = numeric_to_f64(&bytes).unwrap();
355 assert!((result + 123.45).abs() < 0.001);
356 }
357
358 #[test]
359 fn numeric_to_f64_special() {
360 let bytes1 = make_numeric(0, 0, 0xC000, 0, &[]);
362 assert!(numeric_to_f64(&bytes1).unwrap().is_nan());
363
364 let bytes2 = make_numeric(0, 0, 0xD000, 0, &[]);
366 assert_eq!(numeric_to_f64(&bytes2).unwrap(), f64::INFINITY);
367
368 let bytes3 = make_numeric(0, 0, 0xF000, 0, &[]);
370 assert_eq!(numeric_to_f64(&bytes3).unwrap(), f64::NEG_INFINITY);
371 }
372
373 #[test]
374 fn numeric_to_f32_basic() {
375 let bytes = make_numeric(2, 0, 0x0000, 2, &[123, 4500]);
377 let result = numeric_to_f32(&bytes).unwrap();
378 assert!((result - 123.45).abs() < 0.01);
379 }
380
381 #[test]
382 fn numeric_to_f32_special() {
383 let bytes1 = make_numeric(0, 0, 0xC000, 0, &[]);
385 assert!(numeric_to_f32(&bytes1).unwrap().is_nan());
386
387 let bytes2 = make_numeric(0, 0, 0xD000, 0, &[]);
389 assert_eq!(numeric_to_f32(&bytes2).unwrap(), f32::INFINITY);
390
391 let bytes3 = make_numeric(0, 0, 0xF000, 0, &[]);
393 assert_eq!(numeric_to_f32(&bytes3).unwrap(), f32::NEG_INFINITY);
394 }
395}