drizzle_core/conv.rs
1//! Runtime value-conversion helpers shared across dialects.
2//!
3//! Helpers here are intentionally small, dialect-agnostic primitives used by
4//! per-dialect `FromXValue` implementations.
5
6use crate::error::DrizzleError;
7use crate::prelude::format;
8
9/// Convert an `f64` database value to an integer type `T`, rejecting
10/// non-finite, non-integral, and out-of-range inputs with a descriptive error.
11///
12/// The `type_name` is embedded in the error for context (e.g. `"i64"`, `"i32"`).
13///
14/// # Errors
15///
16/// Returns [`DrizzleError::ConversionError`] when:
17/// - `value` is not finite (NaN, ±∞).
18/// - `value` has a non-zero fractional part.
19/// - `value` is outside the representable range of `T` (or of `i128`).
20pub fn checked_float_to_int<T>(value: f64, type_name: &str) -> Result<T, DrizzleError>
21where
22 T: TryFrom<i128>,
23 <T as TryFrom<i128>>::Error: core::fmt::Display,
24{
25 if !value.is_finite() {
26 return Err(DrizzleError::ConversionError(
27 format!("cannot convert non-finite float {value} to {type_name}").into(),
28 ));
29 }
30
31 if value % 1.0 != 0.0 {
32 return Err(DrizzleError::ConversionError(
33 format!("cannot convert non-integer float {value} to {type_name}").into(),
34 ));
35 }
36
37 // `2^127` is exactly representable in f64 and equals `i128::MAX + 1`
38 // (and `-2^127` equals `i128::MIN`). Building the bounds directly in f64
39 // space avoids a precision-losing `i128 as f64` cast. The bit pattern
40 // encodes sign=0, biased_exp=127+1023=1150, mantissa=0.
41 const TWO_POW_127: f64 = f64::from_bits(0x47E0_0000_0000_0000);
42 let two_pow_127 = TWO_POW_127;
43 if value < -two_pow_127 || value >= two_pow_127 {
44 return Err(DrizzleError::ConversionError(
45 format!("float {value} out of range for {type_name}").into(),
46 ));
47 }
48
49 // Safe: `value` is finite, integer-valued, and strictly within `[-2^127, 2^127)`.
50 // Round-trip through a decimal string to avoid an f64-as-i128 cast warning.
51 let int_value: i128 = format!("{value:.0}").parse().map_err(|e| {
52 DrizzleError::ConversionError(
53 format!("float {value} could not be parsed as integer for {type_name}: {e}").into(),
54 )
55 })?;
56 int_value.try_into().map_err(|e| {
57 DrizzleError::ConversionError(
58 format!("float {value} out of range for {type_name}: {e}").into(),
59 )
60 })
61}