Skip to main content

pyo3/types/
float.rs

1use crate::conversion::IntoPyObject;
2#[cfg(feature = "experimental-inspect")]
3use crate::inspect::PyStaticExpr;
4#[cfg(feature = "experimental-inspect")]
5use crate::type_object::PyTypeInfo;
6use crate::{
7    ffi, ffi_ptr_ext::FfiPtrExt, instance::Bound, Borrowed, FromPyObject, PyAny, PyErr, Python,
8};
9use std::convert::Infallible;
10use std::ffi::c_double;
11
12/// Represents a Python `float` object.
13///
14/// Values of this type are accessed via PyO3's smart pointers, e.g. as
15/// [`Py<PyFloat>`][crate::Py] or [`Bound<'py, PyFloat>`][Bound].
16///
17/// For APIs available on `float` objects, see the [`PyFloatMethods`] trait which is implemented for
18/// [`Bound<'py, PyFloat>`][Bound].
19///
20/// You can usually avoid directly working with this type
21/// by using [`IntoPyObject`] and [`extract`][crate::types::PyAnyMethods::extract]
22/// with [`f32`]/[`f64`].
23#[repr(transparent)]
24pub struct PyFloat(PyAny);
25
26pyobject_subclassable_native_type!(PyFloat, crate::ffi::PyFloatObject);
27
28pyobject_native_type!(
29    PyFloat,
30    ffi::PyFloatObject,
31    pyobject_native_static_type_object!(ffi::PyFloat_Type),
32    "builtins",
33    "float",
34    #checkfunction=ffi::PyFloat_Check
35);
36
37impl PyFloat {
38    /// Creates a new Python `float` object.
39    pub fn new(py: Python<'_>, val: c_double) -> Bound<'_, PyFloat> {
40        unsafe {
41            ffi::PyFloat_FromDouble(val)
42                .assume_owned(py)
43                .cast_into_unchecked()
44        }
45    }
46}
47
48/// Implementation of functionality for [`PyFloat`].
49///
50/// These methods are defined for the `Bound<'py, PyFloat>` smart pointer, so to use method call
51/// syntax these methods are separated into a trait, because stable Rust does not yet support
52/// `arbitrary_self_types`.
53#[doc(alias = "PyFloat")]
54pub trait PyFloatMethods<'py>: crate::sealed::Sealed {
55    /// Gets the value of this float.
56    fn value(&self) -> c_double;
57}
58
59impl<'py> PyFloatMethods<'py> for Bound<'py, PyFloat> {
60    fn value(&self) -> c_double {
61        #[cfg(not(Py_LIMITED_API))]
62        unsafe {
63            // Safety: self is PyFloat object
64            ffi::PyFloat_AS_DOUBLE(self.as_ptr())
65        }
66
67        #[cfg(Py_LIMITED_API)]
68        unsafe {
69            ffi::PyFloat_AsDouble(self.as_ptr())
70        }
71    }
72}
73
74impl<'py> IntoPyObject<'py> for f64 {
75    type Target = PyFloat;
76    type Output = Bound<'py, Self::Target>;
77    type Error = Infallible;
78
79    #[cfg(feature = "experimental-inspect")]
80    const OUTPUT_TYPE: PyStaticExpr = PyFloat::TYPE_HINT;
81
82    #[inline]
83    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
84        Ok(PyFloat::new(py, self))
85    }
86}
87
88impl<'py> IntoPyObject<'py> for &f64 {
89    type Target = PyFloat;
90    type Output = Bound<'py, Self::Target>;
91    type Error = Infallible;
92
93    #[cfg(feature = "experimental-inspect")]
94    const OUTPUT_TYPE: PyStaticExpr = f64::OUTPUT_TYPE;
95
96    #[inline]
97    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
98        (*self).into_pyobject(py)
99    }
100}
101
102impl<'py> FromPyObject<'_, 'py> for f64 {
103    type Error = PyErr;
104
105    #[cfg(feature = "experimental-inspect")]
106    const INPUT_TYPE: PyStaticExpr = PyFloat::TYPE_HINT;
107
108    // PyFloat_AsDouble returns -1.0 upon failure
109    #[allow(clippy::float_cmp)]
110    fn extract(obj: Borrowed<'_, 'py, PyAny>) -> Result<Self, Self::Error> {
111        // On non-limited API, .value() uses PyFloat_AS_DOUBLE which
112        // allows us to have an optimized fast path for the case when
113        // we have exactly a `float` object (it's not worth going through
114        // `isinstance` machinery for subclasses).
115        #[cfg(not(Py_LIMITED_API))]
116        if let Ok(float) = obj.cast_exact::<PyFloat>() {
117            return Ok(float.value());
118        }
119
120        let v = unsafe { ffi::PyFloat_AsDouble(obj.as_ptr()) };
121
122        if v == -1.0 {
123            if let Some(err) = PyErr::take(obj.py()) {
124                return Err(err);
125            }
126        }
127
128        Ok(v)
129    }
130}
131
132impl<'py> IntoPyObject<'py> for f32 {
133    type Target = PyFloat;
134    type Output = Bound<'py, Self::Target>;
135    type Error = Infallible;
136
137    #[cfg(feature = "experimental-inspect")]
138    const OUTPUT_TYPE: PyStaticExpr = PyFloat::TYPE_HINT;
139
140    #[inline]
141    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
142        Ok(PyFloat::new(py, self.into()))
143    }
144}
145
146impl<'py> IntoPyObject<'py> for &f32 {
147    type Target = PyFloat;
148    type Output = Bound<'py, Self::Target>;
149    type Error = Infallible;
150
151    #[cfg(feature = "experimental-inspect")]
152    const OUTPUT_TYPE: PyStaticExpr = f32::OUTPUT_TYPE;
153
154    #[inline]
155    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
156        (*self).into_pyobject(py)
157    }
158}
159
160impl<'a, 'py> FromPyObject<'a, 'py> for f32 {
161    type Error = <f64 as FromPyObject<'a, 'py>>::Error;
162
163    #[cfg(feature = "experimental-inspect")]
164    const INPUT_TYPE: PyStaticExpr = PyFloat::TYPE_HINT;
165
166    fn extract(obj: Borrowed<'_, 'py, PyAny>) -> Result<Self, Self::Error> {
167        Ok(obj.extract::<f64>()? as f32)
168    }
169}
170
171macro_rules! impl_partial_eq_for_float {
172    ($float_type: ty) => {
173        impl PartialEq<$float_type> for Bound<'_, PyFloat> {
174            #[inline]
175            fn eq(&self, other: &$float_type) -> bool {
176                self.value() as $float_type == *other
177            }
178        }
179
180        impl PartialEq<$float_type> for &Bound<'_, PyFloat> {
181            #[inline]
182            fn eq(&self, other: &$float_type) -> bool {
183                self.value() as $float_type == *other
184            }
185        }
186
187        impl PartialEq<&$float_type> for Bound<'_, PyFloat> {
188            #[inline]
189            fn eq(&self, other: &&$float_type) -> bool {
190                self.value() as $float_type == **other
191            }
192        }
193
194        impl PartialEq<Bound<'_, PyFloat>> for $float_type {
195            #[inline]
196            fn eq(&self, other: &Bound<'_, PyFloat>) -> bool {
197                other.value() as $float_type == *self
198            }
199        }
200
201        impl PartialEq<&'_ Bound<'_, PyFloat>> for $float_type {
202            #[inline]
203            fn eq(&self, other: &&'_ Bound<'_, PyFloat>) -> bool {
204                other.value() as $float_type == *self
205            }
206        }
207
208        impl PartialEq<Bound<'_, PyFloat>> for &'_ $float_type {
209            #[inline]
210            fn eq(&self, other: &Bound<'_, PyFloat>) -> bool {
211                other.value() as $float_type == **self
212            }
213        }
214
215        impl PartialEq<$float_type> for Borrowed<'_, '_, PyFloat> {
216            #[inline]
217            fn eq(&self, other: &$float_type) -> bool {
218                self.value() as $float_type == *other
219            }
220        }
221
222        impl PartialEq<&$float_type> for Borrowed<'_, '_, PyFloat> {
223            #[inline]
224            fn eq(&self, other: &&$float_type) -> bool {
225                self.value() as $float_type == **other
226            }
227        }
228
229        impl PartialEq<Borrowed<'_, '_, PyFloat>> for $float_type {
230            #[inline]
231            fn eq(&self, other: &Borrowed<'_, '_, PyFloat>) -> bool {
232                other.value() as $float_type == *self
233            }
234        }
235
236        impl PartialEq<Borrowed<'_, '_, PyFloat>> for &$float_type {
237            #[inline]
238            fn eq(&self, other: &Borrowed<'_, '_, PyFloat>) -> bool {
239                other.value() as $float_type == **self
240            }
241        }
242    };
243}
244
245impl_partial_eq_for_float!(f64);
246impl_partial_eq_for_float!(f32);
247
248#[cfg(test)]
249mod tests {
250    use crate::{
251        conversion::IntoPyObject,
252        types::{PyAnyMethods, PyFloat, PyFloatMethods},
253        Python,
254    };
255
256    macro_rules! num_to_py_object_and_back (
257        ($func_name:ident, $t1:ty, $t2:ty) => (
258            #[test]
259            fn $func_name() {
260                use assert_approx_eq::assert_approx_eq;
261
262                Python::attach(|py| {
263
264                let val = 123 as $t1;
265                let obj = val.into_pyobject(py).unwrap();
266                assert_approx_eq!(obj.extract::<$t2>().unwrap(), val as $t2);
267                });
268            }
269        )
270    );
271
272    num_to_py_object_and_back!(to_from_f64, f64, f64);
273    num_to_py_object_and_back!(to_from_f32, f32, f32);
274    num_to_py_object_and_back!(int_to_float, i32, f64);
275
276    #[test]
277    fn test_float_value() {
278        use assert_approx_eq::assert_approx_eq;
279
280        Python::attach(|py| {
281            let v = 1.23f64;
282            let obj = PyFloat::new(py, 1.23);
283            assert_approx_eq!(v, obj.value());
284        });
285    }
286
287    #[test]
288    fn test_pyfloat_comparisons() {
289        Python::attach(|py| {
290            let f_64 = 1.01f64;
291            let py_f64 = PyFloat::new(py, 1.01);
292            let py_f64_ref = &py_f64;
293            let py_f64_borrowed = py_f64.as_borrowed();
294
295            // Bound<'_, PyFloat> == f64 and vice versa
296            assert_eq!(py_f64, f_64);
297            assert_eq!(f_64, py_f64);
298
299            // Bound<'_, PyFloat> == &f64 and vice versa
300            assert_eq!(py_f64, &f_64);
301            assert_eq!(&f_64, py_f64);
302
303            // &Bound<'_, PyFloat> == &f64 and vice versa
304            assert_eq!(py_f64_ref, f_64);
305            assert_eq!(f_64, py_f64_ref);
306
307            // &Bound<'_, PyFloat> == &f64 and vice versa
308            assert_eq!(py_f64_ref, &f_64);
309            assert_eq!(&f_64, py_f64_ref);
310
311            // Borrowed<'_, '_, PyFloat> == f64 and vice versa
312            assert_eq!(py_f64_borrowed, f_64);
313            assert_eq!(f_64, py_f64_borrowed);
314
315            // Borrowed<'_, '_, PyFloat> == &f64 and vice versa
316            assert_eq!(py_f64_borrowed, &f_64);
317            assert_eq!(&f_64, py_f64_borrowed);
318
319            let f_32 = 2.02f32;
320            let py_f32 = PyFloat::new(py, 2.02);
321            let py_f32_ref = &py_f32;
322            let py_f32_borrowed = py_f32.as_borrowed();
323
324            // Bound<'_, PyFloat> == f32 and vice versa
325            assert_eq!(py_f32, f_32);
326            assert_eq!(f_32, py_f32);
327
328            // Bound<'_, PyFloat> == &f32 and vice versa
329            assert_eq!(py_f32, &f_32);
330            assert_eq!(&f_32, py_f32);
331
332            // &Bound<'_, PyFloat> == &f32 and vice versa
333            assert_eq!(py_f32_ref, f_32);
334            assert_eq!(f_32, py_f32_ref);
335
336            // &Bound<'_, PyFloat> == &f32 and vice versa
337            assert_eq!(py_f32_ref, &f_32);
338            assert_eq!(&f_32, py_f32_ref);
339
340            // Borrowed<'_, '_, PyFloat> == f32 and vice versa
341            assert_eq!(py_f32_borrowed, f_32);
342            assert_eq!(f_32, py_f32_borrowed);
343
344            // Borrowed<'_, '_, PyFloat> == &f32 and vice versa
345            assert_eq!(py_f32_borrowed, &f_32);
346            assert_eq!(&f_32, py_f32_borrowed);
347        });
348    }
349}