Skip to main content

ocas_py/
double_float.rs

1//! Python bindings for [`DoubleF64`].
2
3use ocas_domain::DoubleF64;
4use pyo3::exceptions::PyValueError;
5use pyo3::prelude::*;
6
7/// Python wrapper for double-precision floating-point arithmetic.
8///
9/// Provides ~31 decimal digits of precision (~84 binary bits) using
10/// Dekker/Knuth "double-float" arithmetic.
11///
12/// ```python
13/// from ocas import DoubleF64
14///
15/// a = DoubleF64(1.0)
16/// b = DoubleF64(2.0)
17/// print(a + b)  # 3.0
18/// ```
19#[pyclass(name = "DoubleF64", skip_from_py_object)]
20#[derive(Debug, Clone, Copy)]
21pub struct PyDoubleF64 {
22    pub(crate) inner: DoubleF64,
23}
24
25#[pymethods]
26impl PyDoubleF64 {
27    /// Create a new `DoubleF64` from a high and optional low component.
28    ///
29    /// ```python
30    /// x = DoubleF64(3.14)       # from single float
31    /// y = DoubleF64(1.0, 1e-20) # from hi, lo pair
32    /// ```
33    #[new]
34    #[pyo3(signature = (hi, lo = 0.0))]
35    fn new(hi: f64, lo: f64) -> Self {
36        Self {
37            inner: DoubleF64::new(hi, lo),
38        }
39    }
40
41    /// Return the high-order component as a Python `float`.
42    #[allow(clippy::wrong_self_convention)]
43    fn to_f64(&self) -> f64 {
44        self.inner.to_f64()
45    }
46
47    /// Return `(hi, lo)` as a Python tuple.
48    fn components(&self) -> (f64, f64) {
49        (self.inner.hi, self.inner.lo)
50    }
51
52    fn __repr__(&self) -> String {
53        format!("DoubleF64({}, {})", self.inner.hi, self.inner.lo)
54    }
55
56    fn __str__(&self) -> String {
57        format!("{}", self.inner)
58    }
59
60    fn __add__(&self, other: &Self) -> Self {
61        Self {
62            inner: self.inner + other.inner,
63        }
64    }
65
66    fn __sub__(&self, other: &Self) -> Self {
67        Self {
68            inner: self.inner - other.inner,
69        }
70    }
71
72    fn __mul__(&self, other: &Self) -> Self {
73        Self {
74            inner: self.inner * other.inner,
75        }
76    }
77
78    fn __truediv__(&self, other: &Self) -> PyResult<Self> {
79        if other.inner.hi == 0.0 && other.inner.lo == 0.0 {
80            Err(PyValueError::new_err("division by zero"))
81        } else {
82            Ok(Self {
83                inner: self.inner / other.inner,
84            })
85        }
86    }
87
88    fn __neg__(&self) -> Self {
89        Self { inner: -self.inner }
90    }
91
92    fn __abs__(&self) -> Self {
93        Self {
94            inner: self.inner.dabs(),
95        }
96    }
97
98    fn __pow__(&self, exp: i64, _mod: Option<i64>) -> Self {
99        Self {
100            inner: self.inner.powi(exp),
101        }
102    }
103
104    fn __richcmp__(&self, other: &Self, op: pyo3::basic::CompareOp) -> bool {
105        match op {
106            pyo3::basic::CompareOp::Lt => self.inner < other.inner,
107            pyo3::basic::CompareOp::Le => self.inner <= other.inner,
108            pyo3::basic::CompareOp::Eq => self.inner == other.inner,
109            pyo3::basic::CompareOp::Ne => self.inner != other.inner,
110            pyo3::basic::CompareOp::Gt => self.inner > other.inner,
111            pyo3::basic::CompareOp::Ge => self.inner >= other.inner,
112        }
113    }
114
115    fn __hash__(&self) -> u64 {
116        use std::hash::{Hash, Hasher};
117        let mut hasher = std::collections::hash_map::DefaultHasher::new();
118        self.inner.hi.to_bits().hash(&mut hasher);
119        self.inner.lo.to_bits().hash(&mut hasher);
120        hasher.finish()
121    }
122
123    // Transcendental functions
124
125    /// Sine of this value.
126    fn sin(&self) -> Self {
127        Self {
128            inner: self.inner.sin(),
129        }
130    }
131
132    /// Cosine of this value.
133    fn cos(&self) -> Self {
134        Self {
135            inner: self.inner.cos(),
136        }
137    }
138
139    /// Tangent of this value.
140    fn tan(&self) -> Self {
141        Self {
142            inner: self.inner.tan(),
143        }
144    }
145
146    /// Natural exponential (e^x).
147    fn exp(&self) -> Self {
148        Self {
149            inner: self.inner.exp(),
150        }
151    }
152
153    /// Natural logarithm.
154    fn ln(&self) -> PyResult<Self> {
155        if self.inner.hi <= 0.0 {
156            Err(PyValueError::new_err("log of non-positive number"))
157        } else {
158            Ok(Self {
159                inner: self.inner.ln(),
160            })
161        }
162    }
163
164    /// Square root.
165    fn sqrt(&self) -> PyResult<Self> {
166        if self.inner.hi < 0.0 {
167            Err(PyValueError::new_err("sqrt of negative number"))
168        } else {
169            Ok(Self {
170                inner: self.inner.sqrt(),
171            })
172        }
173    }
174}