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 #[cfg(feature = "experimental-inspect")]
106 const INPUT_TYPE: &'static str = "float";
107
108 #[allow(clippy::float_cmp)]
110 fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult<Self> {
111 #[cfg(not(Py_LIMITED_API))]
116 if let Ok(float) = obj.downcast_exact::<PyFloat>() {
117 return Ok(float.value());
118 }
119
120 let v = unsafe { ffi::PyFloat_AsDouble(obj.as_ptr()) };
121
122 if v == -1.0 {
123 if let Some(err) = PyErr::take(obj.py()) {
124 return Err(err);
125 }
126 }
127
128 Ok(v)
129 }
130
131 #[cfg(feature = "experimental-inspect")]
132 fn type_input() -> TypeInfo {
133 Self::type_output()
134 }
135}
136
137impl<'py> IntoPyObject<'py> for f32 {
138 type Target = PyFloat;
139 type Output = Bound<'py, Self::Target>;
140 type Error = Infallible;
141
142 #[inline]
143 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
144 Ok(PyFloat::new(py, self.into()))
145 }
146
147 #[cfg(feature = "experimental-inspect")]
148 fn type_output() -> TypeInfo {
149 TypeInfo::builtin("float")
150 }
151}
152
153impl<'py> IntoPyObject<'py> for &f32 {
154 type Target = PyFloat;
155 type Output = Bound<'py, Self::Target>;
156 type Error = Infallible;
157
158 #[inline]
159 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
160 (*self).into_pyobject(py)
161 }
162
163 #[cfg(feature = "experimental-inspect")]
164 fn type_output() -> TypeInfo {
165 TypeInfo::builtin("float")
166 }
167}
168
169impl<'py> FromPyObject<'py> for f32 {
170 #[cfg(feature = "experimental-inspect")]
171 const INPUT_TYPE: &'static str = "float";
172
173 fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult<Self> {
174 Ok(obj.extract::<f64>()? as f32)
175 }
176
177 #[cfg(feature = "experimental-inspect")]
178 fn type_input() -> TypeInfo {
179 Self::type_output()
180 }
181}
182
183macro_rules! impl_partial_eq_for_float {
184 ($float_type: ty) => {
185 impl PartialEq<$float_type> for Bound<'_, PyFloat> {
186 #[inline]
187 fn eq(&self, other: &$float_type) -> bool {
188 self.value() as $float_type == *other
189 }
190 }
191
192 impl PartialEq<$float_type> for &Bound<'_, PyFloat> {
193 #[inline]
194 fn eq(&self, other: &$float_type) -> bool {
195 self.value() as $float_type == *other
196 }
197 }
198
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<Bound<'_, PyFloat>> for $float_type {
207 #[inline]
208 fn eq(&self, other: &Bound<'_, PyFloat>) -> bool {
209 other.value() as $float_type == *self
210 }
211 }
212
213 impl PartialEq<&'_ Bound<'_, PyFloat>> for $float_type {
214 #[inline]
215 fn eq(&self, other: &&'_ Bound<'_, PyFloat>) -> bool {
216 other.value() as $float_type == *self
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<$float_type> for Borrowed<'_, '_, PyFloat> {
228 #[inline]
229 fn eq(&self, other: &$float_type) -> bool {
230 self.value() as $float_type == *other
231 }
232 }
233
234 impl PartialEq<&$float_type> for Borrowed<'_, '_, PyFloat> {
235 #[inline]
236 fn eq(&self, other: &&$float_type) -> bool {
237 self.value() as $float_type == **other
238 }
239 }
240
241 impl PartialEq<Borrowed<'_, '_, PyFloat>> for $float_type {
242 #[inline]
243 fn eq(&self, other: &Borrowed<'_, '_, PyFloat>) -> bool {
244 other.value() as $float_type == *self
245 }
246 }
247
248 impl PartialEq<Borrowed<'_, '_, PyFloat>> for &$float_type {
249 #[inline]
250 fn eq(&self, other: &Borrowed<'_, '_, PyFloat>) -> bool {
251 other.value() as $float_type == **self
252 }
253 }
254 };
255}
256
257impl_partial_eq_for_float!(f64);
258impl_partial_eq_for_float!(f32);
259
260#[cfg(test)]
261mod tests {
262 use crate::{
263 conversion::IntoPyObject,
264 types::{PyAnyMethods, PyFloat, PyFloatMethods},
265 Python,
266 };
267
268 macro_rules! num_to_py_object_and_back (
269 ($func_name:ident, $t1:ty, $t2:ty) => (
270 #[test]
271 fn $func_name() {
272 use assert_approx_eq::assert_approx_eq;
273
274 Python::attach(|py| {
275
276 let val = 123 as $t1;
277 let obj = val.into_pyobject(py).unwrap();
278 assert_approx_eq!(obj.extract::<$t2>().unwrap(), val as $t2);
279 });
280 }
281 )
282 );
283
284 num_to_py_object_and_back!(to_from_f64, f64, f64);
285 num_to_py_object_and_back!(to_from_f32, f32, f32);
286 num_to_py_object_and_back!(int_to_float, i32, f64);
287
288 #[test]
289 fn test_float_value() {
290 use assert_approx_eq::assert_approx_eq;
291
292 Python::attach(|py| {
293 let v = 1.23f64;
294 let obj = PyFloat::new(py, 1.23);
295 assert_approx_eq!(v, obj.value());
296 });
297 }
298
299 #[test]
300 fn test_pyfloat_comparisons() {
301 Python::attach(|py| {
302 let f_64 = 1.01f64;
303 let py_f64 = PyFloat::new(py, 1.01);
304 let py_f64_ref = &py_f64;
305 let py_f64_borrowed = py_f64.as_borrowed();
306
307 assert_eq!(py_f64, f_64);
309 assert_eq!(f_64, py_f64);
310
311 assert_eq!(py_f64, &f_64);
313 assert_eq!(&f_64, py_f64);
314
315 assert_eq!(py_f64_ref, f_64);
317 assert_eq!(f_64, py_f64_ref);
318
319 assert_eq!(py_f64_ref, &f_64);
321 assert_eq!(&f_64, py_f64_ref);
322
323 assert_eq!(py_f64_borrowed, f_64);
325 assert_eq!(f_64, py_f64_borrowed);
326
327 assert_eq!(py_f64_borrowed, &f_64);
329 assert_eq!(&f_64, py_f64_borrowed);
330
331 let f_32 = 2.02f32;
332 let py_f32 = PyFloat::new(py, 2.02);
333 let py_f32_ref = &py_f32;
334 let py_f32_borrowed = py_f32.as_borrowed();
335
336 assert_eq!(py_f32, f_32);
338 assert_eq!(f_32, py_f32);
339
340 assert_eq!(py_f32, &f_32);
342 assert_eq!(&f_32, py_f32);
343
344 assert_eq!(py_f32_ref, f_32);
346 assert_eq!(f_32, py_f32_ref);
347
348 assert_eq!(py_f32_ref, &f_32);
350 assert_eq!(&f_32, py_f32_ref);
351
352 assert_eq!(py_f32_borrowed, f_32);
354 assert_eq!(f_32, py_f32_borrowed);
355
356 assert_eq!(py_f32_borrowed, &f_32);
358 assert_eq!(&f_32, py_f32_borrowed);
359 });
360 }
361}