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::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`][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                .cast_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    #[cfg(feature = "experimental-inspect")]
78    const OUTPUT_TYPE: &'static str = "float";
79
80    #[inline]
81    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
82        Ok(PyFloat::new(py, self))
83    }
84
85    #[cfg(feature = "experimental-inspect")]
86    fn type_output() -> TypeInfo {
87        TypeInfo::builtin("float")
88    }
89}
90
91impl<'py> IntoPyObject<'py> for &f64 {
92    type Target = PyFloat;
93    type Output = Bound<'py, Self::Target>;
94    type Error = Infallible;
95
96    #[cfg(feature = "experimental-inspect")]
97    const OUTPUT_TYPE: &'static str = f64::OUTPUT_TYPE;
98
99    #[inline]
100    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
101        (*self).into_pyobject(py)
102    }
103
104    #[cfg(feature = "experimental-inspect")]
105    fn type_output() -> TypeInfo {
106        TypeInfo::builtin("float")
107    }
108}
109
110impl<'py> FromPyObject<'py> for f64 {
111    #[cfg(feature = "experimental-inspect")]
112    const INPUT_TYPE: &'static str = "float";
113
114    // PyFloat_AsDouble returns -1.0 upon failure
115    #[allow(clippy::float_cmp)]
116    fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult<Self> {
117        // On non-limited API, .value() uses PyFloat_AS_DOUBLE which
118        // allows us to have an optimized fast path for the case when
119        // we have exactly a `float` object (it's not worth going through
120        // `isinstance` machinery for subclasses).
121        #[cfg(not(Py_LIMITED_API))]
122        if let Ok(float) = obj.cast_exact::<PyFloat>() {
123            return Ok(float.value());
124        }
125
126        let v = unsafe { ffi::PyFloat_AsDouble(obj.as_ptr()) };
127
128        if v == -1.0 {
129            if let Some(err) = PyErr::take(obj.py()) {
130                return Err(err);
131            }
132        }
133
134        Ok(v)
135    }
136
137    #[cfg(feature = "experimental-inspect")]
138    fn type_input() -> TypeInfo {
139        Self::type_output()
140    }
141}
142
143impl<'py> IntoPyObject<'py> for f32 {
144    type Target = PyFloat;
145    type Output = Bound<'py, Self::Target>;
146    type Error = Infallible;
147
148    #[cfg(feature = "experimental-inspect")]
149    const OUTPUT_TYPE: &'static str = "float";
150
151    #[inline]
152    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
153        Ok(PyFloat::new(py, self.into()))
154    }
155
156    #[cfg(feature = "experimental-inspect")]
157    fn type_output() -> TypeInfo {
158        TypeInfo::builtin("float")
159    }
160}
161
162impl<'py> IntoPyObject<'py> for &f32 {
163    type Target = PyFloat;
164    type Output = Bound<'py, Self::Target>;
165    type Error = Infallible;
166
167    #[cfg(feature = "experimental-inspect")]
168    const OUTPUT_TYPE: &'static str = f32::OUTPUT_TYPE;
169
170    #[inline]
171    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
172        (*self).into_pyobject(py)
173    }
174
175    #[cfg(feature = "experimental-inspect")]
176    fn type_output() -> TypeInfo {
177        TypeInfo::builtin("float")
178    }
179}
180
181impl<'py> FromPyObject<'py> for f32 {
182    #[cfg(feature = "experimental-inspect")]
183    const INPUT_TYPE: &'static str = "float";
184
185    fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult<Self> {
186        Ok(obj.extract::<f64>()? as f32)
187    }
188
189    #[cfg(feature = "experimental-inspect")]
190    fn type_input() -> TypeInfo {
191        Self::type_output()
192    }
193}
194
195macro_rules! impl_partial_eq_for_float {
196    ($float_type: ty) => {
197        impl PartialEq<$float_type> for Bound<'_, PyFloat> {
198            #[inline]
199            fn eq(&self, other: &$float_type) -> bool {
200                self.value() as $float_type == *other
201            }
202        }
203
204        impl PartialEq<$float_type> for &Bound<'_, PyFloat> {
205            #[inline]
206            fn eq(&self, other: &$float_type) -> bool {
207                self.value() as $float_type == *other
208            }
209        }
210
211        impl PartialEq<&$float_type> for Bound<'_, PyFloat> {
212            #[inline]
213            fn eq(&self, other: &&$float_type) -> bool {
214                self.value() as $float_type == **other
215            }
216        }
217
218        impl PartialEq<Bound<'_, PyFloat>> for $float_type {
219            #[inline]
220            fn eq(&self, other: &Bound<'_, PyFloat>) -> bool {
221                other.value() as $float_type == *self
222            }
223        }
224
225        impl PartialEq<&'_ Bound<'_, PyFloat>> for $float_type {
226            #[inline]
227            fn eq(&self, other: &&'_ Bound<'_, PyFloat>) -> bool {
228                other.value() as $float_type == *self
229            }
230        }
231
232        impl PartialEq<Bound<'_, PyFloat>> for &'_ $float_type {
233            #[inline]
234            fn eq(&self, other: &Bound<'_, PyFloat>) -> bool {
235                other.value() as $float_type == **self
236            }
237        }
238
239        impl PartialEq<$float_type> for Borrowed<'_, '_, PyFloat> {
240            #[inline]
241            fn eq(&self, other: &$float_type) -> bool {
242                self.value() as $float_type == *other
243            }
244        }
245
246        impl PartialEq<&$float_type> for Borrowed<'_, '_, PyFloat> {
247            #[inline]
248            fn eq(&self, other: &&$float_type) -> bool {
249                self.value() as $float_type == **other
250            }
251        }
252
253        impl PartialEq<Borrowed<'_, '_, PyFloat>> for $float_type {
254            #[inline]
255            fn eq(&self, other: &Borrowed<'_, '_, PyFloat>) -> bool {
256                other.value() as $float_type == *self
257            }
258        }
259
260        impl PartialEq<Borrowed<'_, '_, PyFloat>> for &$float_type {
261            #[inline]
262            fn eq(&self, other: &Borrowed<'_, '_, PyFloat>) -> bool {
263                other.value() as $float_type == **self
264            }
265        }
266    };
267}
268
269impl_partial_eq_for_float!(f64);
270impl_partial_eq_for_float!(f32);
271
272#[cfg(test)]
273mod tests {
274    use crate::{
275        conversion::IntoPyObject,
276        types::{PyAnyMethods, PyFloat, PyFloatMethods},
277        Python,
278    };
279
280    macro_rules! num_to_py_object_and_back (
281        ($func_name:ident, $t1:ty, $t2:ty) => (
282            #[test]
283            fn $func_name() {
284                use assert_approx_eq::assert_approx_eq;
285
286                Python::attach(|py| {
287
288                let val = 123 as $t1;
289                let obj = val.into_pyobject(py).unwrap();
290                assert_approx_eq!(obj.extract::<$t2>().unwrap(), val as $t2);
291                });
292            }
293        )
294    );
295
296    num_to_py_object_and_back!(to_from_f64, f64, f64);
297    num_to_py_object_and_back!(to_from_f32, f32, f32);
298    num_to_py_object_and_back!(int_to_float, i32, f64);
299
300    #[test]
301    fn test_float_value() {
302        use assert_approx_eq::assert_approx_eq;
303
304        Python::attach(|py| {
305            let v = 1.23f64;
306            let obj = PyFloat::new(py, 1.23);
307            assert_approx_eq!(v, obj.value());
308        });
309    }
310
311    #[test]
312    fn test_pyfloat_comparisons() {
313        Python::attach(|py| {
314            let f_64 = 1.01f64;
315            let py_f64 = PyFloat::new(py, 1.01);
316            let py_f64_ref = &py_f64;
317            let py_f64_borrowed = py_f64.as_borrowed();
318
319            // Bound<'_, PyFloat> == f64 and vice versa
320            assert_eq!(py_f64, f_64);
321            assert_eq!(f_64, py_f64);
322
323            // Bound<'_, PyFloat> == &f64 and vice versa
324            assert_eq!(py_f64, &f_64);
325            assert_eq!(&f_64, py_f64);
326
327            // &Bound<'_, PyFloat> == &f64 and vice versa
328            assert_eq!(py_f64_ref, f_64);
329            assert_eq!(f_64, py_f64_ref);
330
331            // &Bound<'_, PyFloat> == &f64 and vice versa
332            assert_eq!(py_f64_ref, &f_64);
333            assert_eq!(&f_64, py_f64_ref);
334
335            // Borrowed<'_, '_, PyFloat> == f64 and vice versa
336            assert_eq!(py_f64_borrowed, f_64);
337            assert_eq!(f_64, py_f64_borrowed);
338
339            // Borrowed<'_, '_, PyFloat> == &f64 and vice versa
340            assert_eq!(py_f64_borrowed, &f_64);
341            assert_eq!(&f_64, py_f64_borrowed);
342
343            let f_32 = 2.02f32;
344            let py_f32 = PyFloat::new(py, 2.02);
345            let py_f32_ref = &py_f32;
346            let py_f32_borrowed = py_f32.as_borrowed();
347
348            // Bound<'_, PyFloat> == f32 and vice versa
349            assert_eq!(py_f32, f_32);
350            assert_eq!(f_32, py_f32);
351
352            // Bound<'_, PyFloat> == &f32 and vice versa
353            assert_eq!(py_f32, &f_32);
354            assert_eq!(&f_32, py_f32);
355
356            // &Bound<'_, PyFloat> == &f32 and vice versa
357            assert_eq!(py_f32_ref, f_32);
358            assert_eq!(f_32, py_f32_ref);
359
360            // &Bound<'_, PyFloat> == &f32 and vice versa
361            assert_eq!(py_f32_ref, &f_32);
362            assert_eq!(&f_32, py_f32_ref);
363
364            // Borrowed<'_, '_, PyFloat> == f32 and vice versa
365            assert_eq!(py_f32_borrowed, f_32);
366            assert_eq!(f_32, py_f32_borrowed);
367
368            // Borrowed<'_, '_, PyFloat> == &f32 and vice versa
369            assert_eq!(py_f32_borrowed, &f_32);
370            assert_eq!(&f_32, py_f32_borrowed);
371        });
372    }
373}