[go: up one dir, main page]

pyo3/types/
float.rs

1use crate::conversion::IntoPyObject;
2#[cfg(feature = "experimental-inspect")]
3use crate::inspect::types::TypeInfo;
4use crate::{
5    ffi, ffi_ptr_ext::FfiPtrExt, instance::Bound, Borrowed, FromPyObject, PyAny, PyErr, Python,
6};
7use std::convert::Infallible;
8use std::ffi::c_double;
9
10/// Represents a Python `float` object.
11///
12/// Values of this type are accessed via PyO3's smart pointers, e.g. as
13/// [`Py<PyFloat>`][crate::Py] or [`Bound<'py, PyFloat>`][Bound].
14///
15/// For APIs available on `float` objects, see the [`PyFloatMethods`] trait which is implemented for
16/// [`Bound<'py, PyFloat>`][Bound].
17///
18/// You can usually avoid directly working with this type
19/// by using [`IntoPyObject`] and [`extract`][crate::types::PyAnyMethods::extract]
20/// with [`f32`]/[`f64`].
21#[repr(transparent)]
22pub struct PyFloat(PyAny);
23
24pyobject_subclassable_native_type!(PyFloat, crate::ffi::PyFloatObject);
25
26pyobject_native_type!(
27    PyFloat,
28    ffi::PyFloatObject,
29    pyobject_native_static_type_object!(ffi::PyFloat_Type),
30    #checkfunction=ffi::PyFloat_Check
31);
32
33impl PyFloat {
34    /// Creates a new Python `float` object.
35    pub fn new(py: Python<'_>, val: c_double) -> Bound<'_, PyFloat> {
36        unsafe {
37            ffi::PyFloat_FromDouble(val)
38                .assume_owned(py)
39                .cast_into_unchecked()
40        }
41    }
42}
43
44/// Implementation of functionality for [`PyFloat`].
45///
46/// These methods are defined for the `Bound<'py, PyFloat>` smart pointer, so to use method call
47/// syntax these methods are separated into a trait, because stable Rust does not yet support
48/// `arbitrary_self_types`.
49#[doc(alias = "PyFloat")]
50pub trait PyFloatMethods<'py>: crate::sealed::Sealed {
51    /// Gets the value of this float.
52    fn value(&self) -> c_double;
53}
54
55impl<'py> PyFloatMethods<'py> for Bound<'py, PyFloat> {
56    fn value(&self) -> c_double {
57        #[cfg(not(Py_LIMITED_API))]
58        unsafe {
59            // Safety: self is PyFloat object
60            ffi::PyFloat_AS_DOUBLE(self.as_ptr())
61        }
62
63        #[cfg(Py_LIMITED_API)]
64        unsafe {
65            ffi::PyFloat_AsDouble(self.as_ptr())
66        }
67    }
68}
69
70impl<'py> IntoPyObject<'py> for f64 {
71    type Target = PyFloat;
72    type Output = Bound<'py, Self::Target>;
73    type Error = Infallible;
74
75    #[cfg(feature = "experimental-inspect")]
76    const OUTPUT_TYPE: &'static str = "float";
77
78    #[inline]
79    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
80        Ok(PyFloat::new(py, self))
81    }
82
83    #[cfg(feature = "experimental-inspect")]
84    fn type_output() -> TypeInfo {
85        TypeInfo::builtin("float")
86    }
87}
88
89impl<'py> IntoPyObject<'py> for &f64 {
90    type Target = PyFloat;
91    type Output = Bound<'py, Self::Target>;
92    type Error = Infallible;
93
94    #[cfg(feature = "experimental-inspect")]
95    const OUTPUT_TYPE: &'static str = f64::OUTPUT_TYPE;
96
97    #[inline]
98    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
99        (*self).into_pyobject(py)
100    }
101
102    #[cfg(feature = "experimental-inspect")]
103    fn type_output() -> TypeInfo {
104        TypeInfo::builtin("float")
105    }
106}
107
108impl<'py> FromPyObject<'_, 'py> for f64 {
109    type Error = PyErr;
110
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(obj: Borrowed<'_, 'py, PyAny>) -> Result<Self, Self::Error> {
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<'a, 'py> FromPyObject<'a, 'py> for f32 {
182    type Error = <f64 as FromPyObject<'a, 'py>>::Error;
183
184    #[cfg(feature = "experimental-inspect")]
185    const INPUT_TYPE: &'static str = "float";
186
187    fn extract(obj: Borrowed<'_, 'py, PyAny>) -> Result<Self, Self::Error> {
188        Ok(obj.extract::<f64>()? as f32)
189    }
190
191    #[cfg(feature = "experimental-inspect")]
192    fn type_input() -> TypeInfo {
193        Self::type_output()
194    }
195}
196
197macro_rules! impl_partial_eq_for_float {
198    ($float_type: ty) => {
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<$float_type> for &Bound<'_, PyFloat> {
207            #[inline]
208            fn eq(&self, other: &$float_type) -> bool {
209                self.value() as $float_type == *other
210            }
211        }
212
213        impl PartialEq<&$float_type> for Bound<'_, PyFloat> {
214            #[inline]
215            fn eq(&self, other: &&$float_type) -> bool {
216                self.value() as $float_type == **other
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<&'_ Bound<'_, PyFloat>> for $float_type {
228            #[inline]
229            fn eq(&self, other: &&'_ Bound<'_, PyFloat>) -> bool {
230                other.value() as $float_type == *self
231            }
232        }
233
234        impl PartialEq<Bound<'_, PyFloat>> for &'_ $float_type {
235            #[inline]
236            fn eq(&self, other: &Bound<'_, PyFloat>) -> bool {
237                other.value() as $float_type == **self
238            }
239        }
240
241        impl PartialEq<$float_type> for Borrowed<'_, '_, PyFloat> {
242            #[inline]
243            fn eq(&self, other: &$float_type) -> bool {
244                self.value() as $float_type == *other
245            }
246        }
247
248        impl PartialEq<&$float_type> for Borrowed<'_, '_, PyFloat> {
249            #[inline]
250            fn eq(&self, other: &&$float_type) -> bool {
251                self.value() as $float_type == **other
252            }
253        }
254
255        impl PartialEq<Borrowed<'_, '_, PyFloat>> for $float_type {
256            #[inline]
257            fn eq(&self, other: &Borrowed<'_, '_, PyFloat>) -> bool {
258                other.value() as $float_type == *self
259            }
260        }
261
262        impl PartialEq<Borrowed<'_, '_, PyFloat>> for &$float_type {
263            #[inline]
264            fn eq(&self, other: &Borrowed<'_, '_, PyFloat>) -> bool {
265                other.value() as $float_type == **self
266            }
267        }
268    };
269}
270
271impl_partial_eq_for_float!(f64);
272impl_partial_eq_for_float!(f32);
273
274#[cfg(test)]
275mod tests {
276    use crate::{
277        conversion::IntoPyObject,
278        types::{PyAnyMethods, PyFloat, PyFloatMethods},
279        Python,
280    };
281
282    macro_rules! num_to_py_object_and_back (
283        ($func_name:ident, $t1:ty, $t2:ty) => (
284            #[test]
285            fn $func_name() {
286                use assert_approx_eq::assert_approx_eq;
287
288                Python::attach(|py| {
289
290                let val = 123 as $t1;
291                let obj = val.into_pyobject(py).unwrap();
292                assert_approx_eq!(obj.extract::<$t2>().unwrap(), val as $t2);
293                });
294            }
295        )
296    );
297
298    num_to_py_object_and_back!(to_from_f64, f64, f64);
299    num_to_py_object_and_back!(to_from_f32, f32, f32);
300    num_to_py_object_and_back!(int_to_float, i32, f64);
301
302    #[test]
303    fn test_float_value() {
304        use assert_approx_eq::assert_approx_eq;
305
306        Python::attach(|py| {
307            let v = 1.23f64;
308            let obj = PyFloat::new(py, 1.23);
309            assert_approx_eq!(v, obj.value());
310        });
311    }
312
313    #[test]
314    fn test_pyfloat_comparisons() {
315        Python::attach(|py| {
316            let f_64 = 1.01f64;
317            let py_f64 = PyFloat::new(py, 1.01);
318            let py_f64_ref = &py_f64;
319            let py_f64_borrowed = py_f64.as_borrowed();
320
321            // Bound<'_, PyFloat> == f64 and vice versa
322            assert_eq!(py_f64, f_64);
323            assert_eq!(f_64, py_f64);
324
325            // Bound<'_, PyFloat> == &f64 and vice versa
326            assert_eq!(py_f64, &f_64);
327            assert_eq!(&f_64, py_f64);
328
329            // &Bound<'_, PyFloat> == &f64 and vice versa
330            assert_eq!(py_f64_ref, f_64);
331            assert_eq!(f_64, py_f64_ref);
332
333            // &Bound<'_, PyFloat> == &f64 and vice versa
334            assert_eq!(py_f64_ref, &f_64);
335            assert_eq!(&f_64, py_f64_ref);
336
337            // Borrowed<'_, '_, PyFloat> == f64 and vice versa
338            assert_eq!(py_f64_borrowed, f_64);
339            assert_eq!(f_64, py_f64_borrowed);
340
341            // Borrowed<'_, '_, PyFloat> == &f64 and vice versa
342            assert_eq!(py_f64_borrowed, &f_64);
343            assert_eq!(&f_64, py_f64_borrowed);
344
345            let f_32 = 2.02f32;
346            let py_f32 = PyFloat::new(py, 2.02);
347            let py_f32_ref = &py_f32;
348            let py_f32_borrowed = py_f32.as_borrowed();
349
350            // Bound<'_, PyFloat> == f32 and vice versa
351            assert_eq!(py_f32, f_32);
352            assert_eq!(f_32, py_f32);
353
354            // Bound<'_, PyFloat> == &f32 and vice versa
355            assert_eq!(py_f32, &f_32);
356            assert_eq!(&f_32, py_f32);
357
358            // &Bound<'_, PyFloat> == &f32 and vice versa
359            assert_eq!(py_f32_ref, f_32);
360            assert_eq!(f_32, py_f32_ref);
361
362            // &Bound<'_, PyFloat> == &f32 and vice versa
363            assert_eq!(py_f32_ref, &f_32);
364            assert_eq!(&f_32, py_f32_ref);
365
366            // Borrowed<'_, '_, PyFloat> == f32 and vice versa
367            assert_eq!(py_f32_borrowed, f_32);
368            assert_eq!(f_32, py_f32_borrowed);
369
370            // Borrowed<'_, '_, PyFloat> == &f32 and vice versa
371            assert_eq!(py_f32_borrowed, &f_32);
372            assert_eq!(&f_32, py_f32_borrowed);
373        });
374    }
375}