pyo3/types/
float.rs

1use super::any::PyAnyMethods;
2#[cfg(feature = "experimental-inspect")]
3use crate::inspect::types::TypeInfo;
4#[cfg(feature = "gil-refs")]
5use crate::PyNativeType;
6use crate::{
7    ffi, ffi_ptr_ext::FfiPtrExt, instance::Bound, FromPyObject, IntoPy, PyAny, PyErr, PyObject,
8    PyResult, Python, ToPyObject,
9};
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 [`ToPyObject`] and [`extract`][PyAnyMethods::extract]
22/// with [`f32`]/[`f64`].
23#[repr(transparent)]
24pub struct PyFloat(PyAny);
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_bound(py: Python<'_>, val: c_double) -> Bound<'_, PyFloat> {
36        unsafe {
37            ffi::PyFloat_FromDouble(val)
38                .assume_owned(py)
39                .downcast_into_unchecked()
40        }
41    }
42}
43
44#[cfg(feature = "gil-refs")]
45impl PyFloat {
46    /// Deprecated form of [`PyFloat::new_bound`].
47    #[inline]
48    #[deprecated(
49        since = "0.21.0",
50        note = "`PyFloat::new` will be replaced by `PyFloat::new_bound` in a future PyO3 version"
51    )]
52    pub fn new(py: Python<'_>, val: f64) -> &'_ Self {
53        Self::new_bound(py, val).into_gil_ref()
54    }
55
56    /// Gets the value of this float.
57    pub fn value(&self) -> c_double {
58        self.as_borrowed().value()
59    }
60}
61
62/// Implementation of functionality for [`PyFloat`].
63///
64/// These methods are defined for the `Bound<'py, PyFloat>` smart pointer, so to use method call
65/// syntax these methods are separated into a trait, because stable Rust does not yet support
66/// `arbitrary_self_types`.
67#[doc(alias = "PyFloat")]
68pub trait PyFloatMethods<'py>: crate::sealed::Sealed {
69    /// Gets the value of this float.
70    fn value(&self) -> c_double;
71}
72
73impl<'py> PyFloatMethods<'py> for Bound<'py, PyFloat> {
74    fn value(&self) -> c_double {
75        #[cfg(not(Py_LIMITED_API))]
76        unsafe {
77            // Safety: self is PyFloat object
78            ffi::PyFloat_AS_DOUBLE(self.as_ptr())
79        }
80
81        #[cfg(Py_LIMITED_API)]
82        unsafe {
83            ffi::PyFloat_AsDouble(self.as_ptr())
84        }
85    }
86}
87
88impl ToPyObject for f64 {
89    fn to_object(&self, py: Python<'_>) -> PyObject {
90        PyFloat::new_bound(py, *self).into()
91    }
92}
93
94impl IntoPy<PyObject> for f64 {
95    fn into_py(self, py: Python<'_>) -> PyObject {
96        PyFloat::new_bound(py, self).into()
97    }
98
99    #[cfg(feature = "experimental-inspect")]
100    fn type_output() -> TypeInfo {
101        TypeInfo::builtin("float")
102    }
103}
104
105impl<'py> FromPyObject<'py> for f64 {
106    // PyFloat_AsDouble returns -1.0 upon failure
107    #![allow(clippy::float_cmp)]
108    fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult<Self> {
109        // On non-limited API, .value() uses PyFloat_AS_DOUBLE which
110        // allows us to have an optimized fast path for the case when
111        // we have exactly a `float` object (it's not worth going through
112        // `isinstance` machinery for subclasses).
113        #[cfg(not(Py_LIMITED_API))]
114        if let Ok(float) = obj.downcast_exact::<PyFloat>() {
115            return Ok(float.value());
116        }
117
118        let v = unsafe { ffi::PyFloat_AsDouble(obj.as_ptr()) };
119
120        if v == -1.0 {
121            if let Some(err) = PyErr::take(obj.py()) {
122                return Err(err);
123            }
124        }
125
126        Ok(v)
127    }
128
129    #[cfg(feature = "experimental-inspect")]
130    fn type_input() -> TypeInfo {
131        Self::type_output()
132    }
133}
134
135impl ToPyObject for f32 {
136    fn to_object(&self, py: Python<'_>) -> PyObject {
137        PyFloat::new_bound(py, f64::from(*self)).into()
138    }
139}
140
141impl IntoPy<PyObject> for f32 {
142    fn into_py(self, py: Python<'_>) -> PyObject {
143        PyFloat::new_bound(py, f64::from(self)).into()
144    }
145
146    #[cfg(feature = "experimental-inspect")]
147    fn type_output() -> TypeInfo {
148        TypeInfo::builtin("float")
149    }
150}
151
152impl<'py> FromPyObject<'py> for f32 {
153    fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult<Self> {
154        Ok(obj.extract::<f64>()? as f32)
155    }
156
157    #[cfg(feature = "experimental-inspect")]
158    fn type_input() -> TypeInfo {
159        Self::type_output()
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    use crate::{
166        types::{PyFloat, PyFloatMethods},
167        Python, ToPyObject,
168    };
169
170    macro_rules! num_to_py_object_and_back (
171        ($func_name:ident, $t1:ty, $t2:ty) => (
172            #[test]
173            fn $func_name() {
174                use assert_approx_eq::assert_approx_eq;
175
176                Python::with_gil(|py| {
177
178                let val = 123 as $t1;
179                let obj = val.to_object(py);
180                assert_approx_eq!(obj.extract::<$t2>(py).unwrap(), val as $t2);
181                });
182            }
183        )
184    );
185
186    num_to_py_object_and_back!(to_from_f64, f64, f64);
187    num_to_py_object_and_back!(to_from_f32, f32, f32);
188    num_to_py_object_and_back!(int_to_float, i32, f64);
189
190    #[test]
191    fn test_float_value() {
192        use assert_approx_eq::assert_approx_eq;
193
194        Python::with_gil(|py| {
195            let v = 1.23f64;
196            let obj = PyFloat::new_bound(py, 1.23);
197            assert_approx_eq!(v, obj.value());
198        });
199    }
200}