pyo3/types/
float.rs

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