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::os::raw::c_double;
11
12#[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 pub fn new(py: Python<'_>, val: c_double) -> Bound<'_, PyFloat> {
38 unsafe {
39 ffi::PyFloat_FromDouble(val)
40 .assume_owned(py)
41 .downcast_into_unchecked()
42 }
43 }
44}
45
46#[doc(alias = "PyFloat")]
52pub trait PyFloatMethods<'py>: crate::sealed::Sealed {
53 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 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 #[inline]
78 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
79 Ok(PyFloat::new(py, self))
80 }
81
82 #[cfg(feature = "experimental-inspect")]
83 fn type_output() -> TypeInfo {
84 TypeInfo::builtin("float")
85 }
86}
87
88impl<'py> IntoPyObject<'py> for &f64 {
89 type Target = PyFloat;
90 type Output = Bound<'py, Self::Target>;
91 type Error = Infallible;
92
93 #[inline]
94 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
95 (*self).into_pyobject(py)
96 }
97
98 #[cfg(feature = "experimental-inspect")]
99 fn type_output() -> TypeInfo {
100 TypeInfo::builtin("float")
101 }
102}
103
104impl<'py> FromPyObject<'py> for f64 {
105 #![allow(clippy::float_cmp)]
107 fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult<Self> {
108 #[cfg(not(Py_LIMITED_API))]
113 if let Ok(float) = obj.downcast_exact::<PyFloat>() {
114 return Ok(float.value());
115 }
116
117 let v = unsafe { ffi::PyFloat_AsDouble(obj.as_ptr()) };
118
119 if v == -1.0 {
120 if let Some(err) = PyErr::take(obj.py()) {
121 return Err(err);
122 }
123 }
124
125 Ok(v)
126 }
127
128 #[cfg(feature = "experimental-inspect")]
129 fn type_input() -> TypeInfo {
130 Self::type_output()
131 }
132}
133
134impl<'py> IntoPyObject<'py> for f32 {
135 type Target = PyFloat;
136 type Output = Bound<'py, Self::Target>;
137 type Error = Infallible;
138
139 #[inline]
140 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
141 Ok(PyFloat::new(py, self.into()))
142 }
143
144 #[cfg(feature = "experimental-inspect")]
145 fn type_output() -> TypeInfo {
146 TypeInfo::builtin("float")
147 }
148}
149
150impl<'py> IntoPyObject<'py> for &f32 {
151 type Target = PyFloat;
152 type Output = Bound<'py, Self::Target>;
153 type Error = Infallible;
154
155 #[inline]
156 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
157 (*self).into_pyobject(py)
158 }
159
160 #[cfg(feature = "experimental-inspect")]
161 fn type_output() -> TypeInfo {
162 TypeInfo::builtin("float")
163 }
164}
165
166impl<'py> FromPyObject<'py> for f32 {
167 fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult<Self> {
168 Ok(obj.extract::<f64>()? as f32)
169 }
170
171 #[cfg(feature = "experimental-inspect")]
172 fn type_input() -> TypeInfo {
173 Self::type_output()
174 }
175}
176
177macro_rules! impl_partial_eq_for_float {
178 ($float_type: ty) => {
179 impl PartialEq<$float_type> for Bound<'_, PyFloat> {
180 #[inline]
181 fn eq(&self, other: &$float_type) -> bool {
182 self.value() as $float_type == *other
183 }
184 }
185
186 impl PartialEq<$float_type> for &Bound<'_, PyFloat> {
187 #[inline]
188 fn eq(&self, other: &$float_type) -> bool {
189 self.value() as $float_type == *other
190 }
191 }
192
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<Bound<'_, PyFloat>> for $float_type {
201 #[inline]
202 fn eq(&self, other: &Bound<'_, PyFloat>) -> bool {
203 other.value() as $float_type == *self
204 }
205 }
206
207 impl PartialEq<&'_ Bound<'_, PyFloat>> for $float_type {
208 #[inline]
209 fn eq(&self, other: &&'_ Bound<'_, PyFloat>) -> bool {
210 other.value() as $float_type == *self
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<$float_type> for Borrowed<'_, '_, PyFloat> {
222 #[inline]
223 fn eq(&self, other: &$float_type) -> bool {
224 self.value() as $float_type == *other
225 }
226 }
227
228 impl PartialEq<&$float_type> for Borrowed<'_, '_, PyFloat> {
229 #[inline]
230 fn eq(&self, other: &&$float_type) -> bool {
231 self.value() as $float_type == **other
232 }
233 }
234
235 impl PartialEq<Borrowed<'_, '_, PyFloat>> for $float_type {
236 #[inline]
237 fn eq(&self, other: &Borrowed<'_, '_, PyFloat>) -> bool {
238 other.value() as $float_type == *self
239 }
240 }
241
242 impl PartialEq<Borrowed<'_, '_, PyFloat>> for &$float_type {
243 #[inline]
244 fn eq(&self, other: &Borrowed<'_, '_, PyFloat>) -> bool {
245 other.value() as $float_type == **self
246 }
247 }
248 };
249}
250
251impl_partial_eq_for_float!(f64);
252impl_partial_eq_for_float!(f32);
253
254#[cfg(test)]
255mod tests {
256 use crate::{
257 conversion::IntoPyObject,
258 types::{PyAnyMethods, PyFloat, PyFloatMethods},
259 Python,
260 };
261
262 macro_rules! num_to_py_object_and_back (
263 ($func_name:ident, $t1:ty, $t2:ty) => (
264 #[test]
265 fn $func_name() {
266 use assert_approx_eq::assert_approx_eq;
267
268 Python::with_gil(|py| {
269
270 let val = 123 as $t1;
271 let obj = val.into_pyobject(py).unwrap();
272 assert_approx_eq!(obj.extract::<$t2>().unwrap(), val as $t2);
273 });
274 }
275 )
276 );
277
278 num_to_py_object_and_back!(to_from_f64, f64, f64);
279 num_to_py_object_and_back!(to_from_f32, f32, f32);
280 num_to_py_object_and_back!(int_to_float, i32, f64);
281
282 #[test]
283 fn test_float_value() {
284 use assert_approx_eq::assert_approx_eq;
285
286 Python::with_gil(|py| {
287 let v = 1.23f64;
288 let obj = PyFloat::new(py, 1.23);
289 assert_approx_eq!(v, obj.value());
290 });
291 }
292
293 #[test]
294 fn test_pyfloat_comparisons() {
295 Python::with_gil(|py| {
296 let f_64 = 1.01f64;
297 let py_f64 = PyFloat::new(py, 1.01);
298 let py_f64_ref = &py_f64;
299 let py_f64_borrowed = py_f64.as_borrowed();
300
301 assert_eq!(py_f64, f_64);
303 assert_eq!(f_64, py_f64);
304
305 assert_eq!(py_f64, &f_64);
307 assert_eq!(&f_64, py_f64);
308
309 assert_eq!(py_f64_ref, f_64);
311 assert_eq!(f_64, py_f64_ref);
312
313 assert_eq!(py_f64_ref, &f_64);
315 assert_eq!(&f_64, py_f64_ref);
316
317 assert_eq!(py_f64_borrowed, f_64);
319 assert_eq!(f_64, py_f64_borrowed);
320
321 assert_eq!(py_f64_borrowed, &f_64);
323 assert_eq!(&f_64, py_f64_borrowed);
324
325 let f_32 = 2.02f32;
326 let py_f32 = PyFloat::new(py, 2.02);
327 let py_f32_ref = &py_f32;
328 let py_f32_borrowed = py_f32.as_borrowed();
329
330 assert_eq!(py_f32, f_32);
332 assert_eq!(f_32, py_f32);
333
334 assert_eq!(py_f32, &f_32);
336 assert_eq!(&f_32, py_f32);
337
338 assert_eq!(py_f32_ref, f_32);
340 assert_eq!(f_32, py_f32_ref);
341
342 assert_eq!(py_f32_ref, &f_32);
344 assert_eq!(&f_32, py_f32_ref);
345
346 assert_eq!(py_f32_borrowed, f_32);
348 assert_eq!(f_32, py_f32_borrowed);
349
350 assert_eq!(py_f32_borrowed, &f_32);
352 assert_eq!(&f_32, py_f32_borrowed);
353 });
354 }
355}