pyo3/types/
float.rs

1use super::any::PyAnyMethods;
2use crate::conversion::IntoPyObject;
3#[cfg(feature = "experimental-inspect")]
4use crate::inspect::types::TypeInfo;
5use crate::{
6    ffi, ffi_ptr_ext::FfiPtrExt, instance::Bound, Borrowed, FromPyObject, PyAny, PyErr, PyResult,
7    Python,
8};
9use std::convert::Infallible;
10use std::os::raw::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`][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    #checkfunction=ffi::PyFloat_Check
33);
34
35impl PyFloat {
36    /// Creates a new Python `float` object.
37    pub fn new(py: Python<'_>, val: c_double) -> Bound<'_, PyFloat> {
38        unsafe {
39            ffi::PyFloat_FromDouble(val)
40                .assume_owned(py)
41                .downcast_into_unchecked()
42        }
43    }
44}
45
46/// Implementation of functionality for [`PyFloat`].
47///
48/// These methods are defined for the `Bound<'py, PyFloat>` smart pointer, so to use method call
49/// syntax these methods are separated into a trait, because stable Rust does not yet support
50/// `arbitrary_self_types`.
51#[doc(alias = "PyFloat")]
52pub trait PyFloatMethods<'py>: crate::sealed::Sealed {
53    /// Gets the value of this float.
54    fn value(&self) -> c_double;
55}
56
57impl<'py> PyFloatMethods<'py> for Bound<'py, PyFloat> {
58    fn value(&self) -> c_double {
59        #[cfg(not(Py_LIMITED_API))]
60        unsafe {
61            // Safety: self is PyFloat object
62            ffi::PyFloat_AS_DOUBLE(self.as_ptr())
63        }
64
65        #[cfg(Py_LIMITED_API)]
66        unsafe {
67            ffi::PyFloat_AsDouble(self.as_ptr())
68        }
69    }
70}
71
72impl<'py> IntoPyObject<'py> for f64 {
73    type Target = PyFloat;
74    type Output = Bound<'py, Self::Target>;
75    type Error = Infallible;
76
77    #[inline]
78    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
79        Ok(PyFloat::new(py, self))
80    }
81
82    #[cfg(feature = "experimental-inspect")]
83    fn type_output() -> TypeInfo {
84        TypeInfo::builtin("float")
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    #[inline]
94    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
95        (*self).into_pyobject(py)
96    }
97
98    #[cfg(feature = "experimental-inspect")]
99    fn type_output() -> TypeInfo {
100        TypeInfo::builtin("float")
101    }
102}
103
104impl<'py> FromPyObject<'py> for f64 {
105    #[cfg(feature = "experimental-inspect")]
106    const INPUT_TYPE: &'static str = "float";
107
108    // PyFloat_AsDouble returns -1.0 upon failure
109    #[allow(clippy::float_cmp)]
110    fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult<Self> {
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.downcast_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    #[cfg(feature = "experimental-inspect")]
132    fn type_input() -> TypeInfo {
133        Self::type_output()
134    }
135}
136
137impl<'py> IntoPyObject<'py> for f32 {
138    type Target = PyFloat;
139    type Output = Bound<'py, Self::Target>;
140    type Error = Infallible;
141
142    #[inline]
143    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
144        Ok(PyFloat::new(py, self.into()))
145    }
146
147    #[cfg(feature = "experimental-inspect")]
148    fn type_output() -> TypeInfo {
149        TypeInfo::builtin("float")
150    }
151}
152
153impl<'py> IntoPyObject<'py> for &f32 {
154    type Target = PyFloat;
155    type Output = Bound<'py, Self::Target>;
156    type Error = Infallible;
157
158    #[inline]
159    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
160        (*self).into_pyobject(py)
161    }
162
163    #[cfg(feature = "experimental-inspect")]
164    fn type_output() -> TypeInfo {
165        TypeInfo::builtin("float")
166    }
167}
168
169impl<'py> FromPyObject<'py> for f32 {
170    #[cfg(feature = "experimental-inspect")]
171    const INPUT_TYPE: &'static str = "float";
172
173    fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult<Self> {
174        Ok(obj.extract::<f64>()? as f32)
175    }
176
177    #[cfg(feature = "experimental-inspect")]
178    fn type_input() -> TypeInfo {
179        Self::type_output()
180    }
181}
182
183macro_rules! impl_partial_eq_for_float {
184    ($float_type: ty) => {
185        impl PartialEq<$float_type> for Bound<'_, PyFloat> {
186            #[inline]
187            fn eq(&self, other: &$float_type) -> bool {
188                self.value() as $float_type == *other
189            }
190        }
191
192        impl PartialEq<$float_type> for &Bound<'_, PyFloat> {
193            #[inline]
194            fn eq(&self, other: &$float_type) -> bool {
195                self.value() as $float_type == *other
196            }
197        }
198
199        impl PartialEq<&$float_type> for Bound<'_, PyFloat> {
200            #[inline]
201            fn eq(&self, other: &&$float_type) -> bool {
202                self.value() as $float_type == **other
203            }
204        }
205
206        impl PartialEq<Bound<'_, PyFloat>> for $float_type {
207            #[inline]
208            fn eq(&self, other: &Bound<'_, PyFloat>) -> bool {
209                other.value() as $float_type == *self
210            }
211        }
212
213        impl PartialEq<&'_ Bound<'_, PyFloat>> for $float_type {
214            #[inline]
215            fn eq(&self, other: &&'_ Bound<'_, PyFloat>) -> bool {
216                other.value() as $float_type == *self
217            }
218        }
219
220        impl PartialEq<Bound<'_, PyFloat>> for &'_ $float_type {
221            #[inline]
222            fn eq(&self, other: &Bound<'_, PyFloat>) -> bool {
223                other.value() as $float_type == **self
224            }
225        }
226
227        impl PartialEq<$float_type> for Borrowed<'_, '_, PyFloat> {
228            #[inline]
229            fn eq(&self, other: &$float_type) -> bool {
230                self.value() as $float_type == *other
231            }
232        }
233
234        impl PartialEq<&$float_type> for Borrowed<'_, '_, PyFloat> {
235            #[inline]
236            fn eq(&self, other: &&$float_type) -> bool {
237                self.value() as $float_type == **other
238            }
239        }
240
241        impl PartialEq<Borrowed<'_, '_, PyFloat>> for $float_type {
242            #[inline]
243            fn eq(&self, other: &Borrowed<'_, '_, PyFloat>) -> bool {
244                other.value() as $float_type == *self
245            }
246        }
247
248        impl PartialEq<Borrowed<'_, '_, PyFloat>> for &$float_type {
249            #[inline]
250            fn eq(&self, other: &Borrowed<'_, '_, PyFloat>) -> bool {
251                other.value() as $float_type == **self
252            }
253        }
254    };
255}
256
257impl_partial_eq_for_float!(f64);
258impl_partial_eq_for_float!(f32);
259
260#[cfg(test)]
261mod tests {
262    use crate::{
263        conversion::IntoPyObject,
264        types::{PyAnyMethods, PyFloat, PyFloatMethods},
265        Python,
266    };
267
268    macro_rules! num_to_py_object_and_back (
269        ($func_name:ident, $t1:ty, $t2:ty) => (
270            #[test]
271            fn $func_name() {
272                use assert_approx_eq::assert_approx_eq;
273
274                Python::attach(|py| {
275
276                let val = 123 as $t1;
277                let obj = val.into_pyobject(py).unwrap();
278                assert_approx_eq!(obj.extract::<$t2>().unwrap(), val as $t2);
279                });
280            }
281        )
282    );
283
284    num_to_py_object_and_back!(to_from_f64, f64, f64);
285    num_to_py_object_and_back!(to_from_f32, f32, f32);
286    num_to_py_object_and_back!(int_to_float, i32, f64);
287
288    #[test]
289    fn test_float_value() {
290        use assert_approx_eq::assert_approx_eq;
291
292        Python::attach(|py| {
293            let v = 1.23f64;
294            let obj = PyFloat::new(py, 1.23);
295            assert_approx_eq!(v, obj.value());
296        });
297    }
298
299    #[test]
300    fn test_pyfloat_comparisons() {
301        Python::attach(|py| {
302            let f_64 = 1.01f64;
303            let py_f64 = PyFloat::new(py, 1.01);
304            let py_f64_ref = &py_f64;
305            let py_f64_borrowed = py_f64.as_borrowed();
306
307            // Bound<'_, PyFloat> == f64 and vice versa
308            assert_eq!(py_f64, f_64);
309            assert_eq!(f_64, py_f64);
310
311            // Bound<'_, PyFloat> == &f64 and vice versa
312            assert_eq!(py_f64, &f_64);
313            assert_eq!(&f_64, py_f64);
314
315            // &Bound<'_, PyFloat> == &f64 and vice versa
316            assert_eq!(py_f64_ref, f_64);
317            assert_eq!(f_64, py_f64_ref);
318
319            // &Bound<'_, PyFloat> == &f64 and vice versa
320            assert_eq!(py_f64_ref, &f_64);
321            assert_eq!(&f_64, py_f64_ref);
322
323            // Borrowed<'_, '_, PyFloat> == f64 and vice versa
324            assert_eq!(py_f64_borrowed, f_64);
325            assert_eq!(f_64, py_f64_borrowed);
326
327            // Borrowed<'_, '_, PyFloat> == &f64 and vice versa
328            assert_eq!(py_f64_borrowed, &f_64);
329            assert_eq!(&f_64, py_f64_borrowed);
330
331            let f_32 = 2.02f32;
332            let py_f32 = PyFloat::new(py, 2.02);
333            let py_f32_ref = &py_f32;
334            let py_f32_borrowed = py_f32.as_borrowed();
335
336            // Bound<'_, PyFloat> == f32 and vice versa
337            assert_eq!(py_f32, f_32);
338            assert_eq!(f_32, py_f32);
339
340            // Bound<'_, PyFloat> == &f32 and vice versa
341            assert_eq!(py_f32, &f_32);
342            assert_eq!(&f_32, py_f32);
343
344            // &Bound<'_, PyFloat> == &f32 and vice versa
345            assert_eq!(py_f32_ref, f_32);
346            assert_eq!(f_32, py_f32_ref);
347
348            // &Bound<'_, PyFloat> == &f32 and vice versa
349            assert_eq!(py_f32_ref, &f_32);
350            assert_eq!(&f_32, py_f32_ref);
351
352            // Borrowed<'_, '_, PyFloat> == f32 and vice versa
353            assert_eq!(py_f32_borrowed, f_32);
354            assert_eq!(f_32, py_f32_borrowed);
355
356            // Borrowed<'_, '_, PyFloat> == &f32 and vice versa
357            assert_eq!(py_f32_borrowed, &f_32);
358            assert_eq!(&f_32, py_f32_borrowed);
359        });
360    }
361}
⚠️ Internal Docs ⚠️ Not Public API 👉 Official Docs Here