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