Skip to main content

r/interpreter/
coerce.rs

1//! Safe numeric conversion helpers.
2//!
3//! Only contains helpers for conversions where std's `From`/`TryFrom` traits
4//! don't exist. For integer↔integer conversions, use `From`/`TryFrom` directly
5//! (with `?` — `From<TryFromIntError>` is implemented for `RError`).
6
7use super::value::{RError, RErrorKind};
8
9// region: float → integer (no std TryFrom for f64 → int)
10
11/// `f64 → i64` — truncation toward zero (R `as.integer()` semantics).
12/// Fails for NaN, ±Inf, or values outside `i64` range.
13#[inline]
14pub fn f64_to_i64(v: f64) -> Result<i64, RError> {
15    if v.is_nan() {
16        return Err(RError::new(
17            RErrorKind::Type,
18            "NaN cannot be converted to integer",
19        ));
20    }
21    if v.is_infinite() {
22        return Err(RError::new(
23            RErrorKind::Type,
24            format!(
25                "{}Inf cannot be converted to integer",
26                if v < 0.0 { "-" } else { "" }
27            ),
28        ));
29    }
30    // Check range before truncating
31    if v > i64::MAX as f64 || v < i64::MIN as f64 {
32        return Err(RError::new(
33            RErrorKind::Type,
34            format!("value {v} out of integer range"),
35        ));
36    }
37    Ok(v as i64) // intentional truncation toward zero
38}
39
40/// `f64 → usize` — truncation toward zero, then check non-negative.
41#[inline]
42#[allow(dead_code)]
43pub fn f64_to_usize(v: f64) -> Result<usize, RError> {
44    let i = f64_to_i64(v)?;
45    Ok(usize::try_from(i)?)
46}
47
48/// `f64 → u64` — truncation toward zero, then check non-negative.
49#[inline]
50pub fn f64_to_u64(v: f64) -> Result<u64, RError> {
51    let i = f64_to_i64(v)?;
52    Ok(u64::try_from(i)?)
53}
54
55/// `f64 → i32` — truncation toward zero, then check range.
56#[inline]
57pub fn f64_to_i32(v: f64) -> Result<i32, RError> {
58    let i = f64_to_i64(v)?;
59    Ok(i32::try_from(i)?)
60}
61
62/// `f64 → u32` — truncation toward zero, then check range.
63#[inline]
64#[allow(dead_code)]
65pub fn f64_to_u32(v: f64) -> Result<u32, RError> {
66    let i = f64_to_i64(v)?;
67    Ok(u32::try_from(i)?)
68}
69
70// endregion
71
72// region: integer → float (lossy but always succeeds — no std From)
73
74/// `i64 → f64` — always produces a valid f64, but may lose precision
75/// for `|v| > 2^53`. This matches R semantics where `as.double()` on an
76/// integer always succeeds.
77#[inline]
78pub fn i64_to_f64(v: i64) -> f64 {
79    v as f64
80}
81
82/// `usize → f64` — always produces a valid f64, but may lose precision
83/// for `v > 2^53`.
84#[inline]
85pub fn usize_to_f64(v: usize) -> f64 {
86    v as f64
87}
88
89/// `u64 → f64` — always produces a valid f64, but may lose precision
90/// for `v > 2^53`.
91#[inline]
92pub fn u64_to_f64(v: u64) -> f64 {
93    v as f64
94}
95
96// endregion