Skip to main content

tenferro_df64_proof/
conversion.rs

1//! Directed value conversions between the external scalar and `f64`.
2//!
3//! A conversion is a value operation with a declared rounding, range, and
4//! destination allocation. It is not automatic promotion, and it creates no
5//! multi-hop rule: every pair must exist on its own.
6
7use tenferro_tensor::Tensor;
8use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
9
10use crate::Df64;
11
12/// Read the external `Df64` payload of `tensor` by its own element type.
13///
14/// # Errors
15///
16/// Returns [`tenferro_tensor::Error::UnsupportedDType`] when the tensor is not an
17/// externally defined `Df64` value.
18fn df64_values<'a>(tensor: &'a Tensor, op: &'static str) -> tenferro_tensor::Result<&'a [Df64]> {
19    match tensor.external_payload() {
20        Some(payload) => payload
21            .downcast_ref::<Df64>()
22            .map(TypedTensor::<Df64, DynRank, Host>::as_slice)
23            .ok_or_else(|| {
24                tenferro_tensor::Error::unsupported_dtype(
25                    op,
26                    tensor.dtype(),
27                    "the payload holds a different external element type",
28                )
29            }),
30        None => Err(tenferro_tensor::Error::unsupported_dtype(
31            op,
32            tensor.dtype(),
33            "a directed Df64 conversion takes an externally defined Df64 tensor",
34        )),
35    }
36}
37
38/// Read the layout-accessible `f64` values of `tensor`.
39///
40/// # Errors
41///
42/// Returns [`tenferro_tensor::Error::UnsupportedDType`] when the tensor does not
43/// hold `f64` values, and [`tenferro_tensor::Error::RuntimeState`] when they are
44/// not reachable as one borrowed slice.
45fn f64_values<'a>(tensor: &'a Tensor, op: &'static str) -> tenferro_tensor::Result<&'a [f64]> {
46    if tensor.dtype() != tenferro_tensor::DType::F64 {
47        return Err(tenferro_tensor::Error::unsupported_dtype(
48            op,
49            tensor.dtype(),
50            "a directed f64 conversion takes an f64 tensor",
51        ));
52    }
53    tensor
54        .as_slice::<f64>()
55        .map_err(|source| tenferro_tensor::Error::runtime_state_source(op, source))
56}
57
58/// Convert an external `Df64` tensor to an `f64` tensor, low component included.
59///
60/// **Rounding:** the low component participates, so the result is the `f64`
61/// nearest to `hi + lo` with the usual round-to-nearest, ties-to-even rule. This
62/// is not the truncation to `hi` that reinterpreting the payload would give.
63///
64/// **Range:** an infinite or NaN component propagates through the sum by IEEE
65/// arithmetic, and no range error is raised.
66///
67/// **Allocation:** a new `f64` tensor with the same shape is allocated at the
68/// destination; the input payload is neither consumed nor modified.
69///
70/// # Errors
71///
72/// Returns [`tenferro_tensor::Error::UnsupportedDType`] when `tensor` is not an
73/// externally defined `Df64` value.
74///
75/// # Examples
76///
77/// ```rust
78/// use tenferro_df64_proof::{conversion, Df64};
79/// use tenferro_tensor::Tensor;
80/// use tenferro_tensor::{DynRank, ErasedHostTensor, Host, TypedTensor};
81///
82/// let low = Df64 { hi: 1.0, lo: 2f64.powi(-52) };
83/// let tensor = Tensor::external(ErasedHostTensor::new(
84///     TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(vec![1], vec![low])?,
85/// ));
86///
87/// // The low component is part of the value, so it is part of the result.
88/// assert_eq!(conversion::to_f64(&tensor)?.as_slice::<f64>()?, &[1.0 + 2f64.powi(-52)]);
89/// # Ok::<(), Box<dyn std::error::Error>>(())
90/// ```
91pub fn to_f64(tensor: &Tensor) -> tenferro_tensor::Result<Tensor> {
92    let values = df64_values(tensor, "df64_to_f64")?;
93    let narrowed: Vec<f64> = values.iter().map(|value| value.hi + value.lo).collect();
94    Tensor::from_vec_col_major(tensor.shape().to_vec(), narrowed)
95        .map_err(|source| tenferro_tensor::Error::runtime_state_source("df64_to_f64", source))
96}
97
98/// Convert an `f64` tensor to an external `Df64` tensor.
99///
100/// **Rounding:** exact. Every `f64` value is a `Df64` value with a zero low
101/// component, so the result's high component is the input bit for bit and its
102/// low component is zero. Information a previous narrowing discarded is not
103/// recovered here.
104///
105/// **Range:** the transformation preserves `f64` infinity and NaN unchanged.
106///
107/// **Allocation:** a new externally defined tensor with the same shape is
108/// allocated at the destination; the input is neither consumed nor modified.
109///
110/// # Errors
111///
112/// Returns [`tenferro_tensor::Error::UnsupportedDType`] when `tensor` is not an
113/// `f64` value, and [`tenferro_tensor::Error::RuntimeState`] when its values are
114/// not reachable as one borrowed slice.
115///
116/// # Examples
117///
118/// ```rust
119/// use tenferro_df64_proof::{conversion, Df64};
120/// use tenferro_tensor::Tensor;
121///
122/// let tensor = Tensor::from_vec_col_major(vec![2], vec![1.0_f64, 2.0])?;
123/// let widened = conversion::to_df64(&tensor)?;
124///
125/// let payload = widened.external_payload().expect("an external payload");
126/// let values = payload.downcast_ref::<Df64>().expect("the payload type");
127/// assert_eq!(values.as_slice(), &[Df64::from_f64(1.0), Df64::from_f64(2.0)]);
128/// # Ok::<(), Box<dyn std::error::Error>>(())
129/// ```
130pub fn to_df64(tensor: &Tensor) -> tenferro_tensor::Result<Tensor> {
131    let values = f64_values(tensor, "f64_to_df64")?;
132    let widened: Vec<Df64> = values.iter().copied().map(Df64::from_f64).collect();
133    let payload =
134        TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(tensor.shape().to_vec(), widened)?;
135    Ok(Tensor::external(ErasedHostTensor::new(payload)))
136}