1#![cfg(feature = "ordered-float")]
2#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"ordered-float\"] }")]
17use crate::conversion::IntoPyObject;
56use crate::exceptions::PyValueError;
57#[cfg(feature = "experimental-inspect")]
58use crate::inspect::PyStaticExpr;
59use crate::platform::prelude::*;
60use crate::types::PyFloat;
61use crate::{Borrowed, Bound, FromPyObject, PyAny, Python};
62use core::convert::Infallible;
63use ordered_float::{NotNan, OrderedFloat};
64
65macro_rules! float_conversions {
66 ($wrapper:ident, $float_type:ty, $constructor:expr) => {
67 impl<'a, 'py> FromPyObject<'a, 'py> for $wrapper<$float_type> {
68 type Error = <$float_type as FromPyObject<'a, 'py>>::Error;
69
70 #[cfg(feature = "experimental-inspect")]
71 const INPUT_TYPE: PyStaticExpr = <$float_type>::INPUT_TYPE;
72
73 fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
74 let val: $float_type = obj.extract()?;
75 $constructor(val)
76 }
77 }
78
79 impl<'py> IntoPyObject<'py> for $wrapper<$float_type> {
80 type Target = PyFloat;
81 type Output = Bound<'py, Self::Target>;
82 type Error = Infallible;
83
84 #[cfg(feature = "experimental-inspect")]
85 const OUTPUT_TYPE: PyStaticExpr = <$float_type>::OUTPUT_TYPE;
86
87 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
88 self.into_inner().into_pyobject(py)
89 }
90 }
91
92 impl<'py> IntoPyObject<'py> for &$wrapper<$float_type> {
93 type Target = PyFloat;
94 type Output = Bound<'py, Self::Target>;
95 type Error = Infallible;
96
97 #[cfg(feature = "experimental-inspect")]
98 const OUTPUT_TYPE: PyStaticExpr = <$wrapper<$float_type>>::OUTPUT_TYPE;
99
100 #[inline]
101 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
102 (*self).into_pyobject(py)
103 }
104 }
105 };
106}
107float_conversions!(OrderedFloat, f32, |val| Ok(OrderedFloat(val)));
108float_conversions!(OrderedFloat, f64, |val| Ok(OrderedFloat(val)));
109float_conversions!(NotNan, f32, |val| NotNan::new(val)
110 .map_err(|e| PyValueError::new_err(e.to_string())));
111float_conversions!(NotNan, f64, |val| NotNan::new(val)
112 .map_err(|e| PyValueError::new_err(e.to_string())));
113
114#[cfg(test)]
115mod test_ordered_float {
116 use super::*;
117 use crate::types::PyAnyMethods;
118 use crate::types::dict::IntoPyDict;
119 use alloc::ffi::CString;
120 use core::ffi::CStr;
121
122 #[cfg(not(target_arch = "wasm32"))]
123 use proptest::prelude::*;
124
125 fn py_run<'py>(py: Python<'py>, script: &CStr, locals: impl IntoPyDict<'py>) {
126 py.run(script, None, Some(&locals.into_py_dict(py).unwrap()))
127 .unwrap()
128 }
129
130 macro_rules! float_roundtrip_tests {
131 ($wrapper:ident, $float_type:ty, $constructor:expr, $standard_test:ident, $wasm_test:ident, $infinity_test:ident, $zero_test:ident) => {
132 #[cfg(not(target_arch = "wasm32"))]
133 proptest! {
134 #[test]
135 fn $standard_test(inner_f: $float_type) {
136 let f = $constructor(inner_f);
137
138 Python::attach(|py| {
139 let f_py: Bound<'_, PyFloat> = f.into_pyobject(py).unwrap();
140
141 py_run(py, &CString::new(format!(
142 "import math\nassert math.isclose(f_py, {})",
143 inner_f as f64 )).unwrap(), [("f_py", &f_py)]);
146
147 let roundtripped_f: $wrapper<$float_type> = f_py.extract().unwrap();
148
149 assert_eq!(f, roundtripped_f);
150 })
151 }
152 }
153
154 #[cfg(target_arch = "wasm32")]
155 #[test]
156 fn $wasm_test() {
157 let inner_f = 10.0;
158 let f = $constructor(inner_f);
159
160 Python::attach(|py| {
161 let f_py: Bound<'_, PyFloat> = f.into_pyobject(py).unwrap();
162
163 py_run(
164 py,
165 &CString::new(format!(
166 "import math\nassert math.isclose(f_py, {})",
167 inner_f as f64 ))
170 .unwrap(),
171 [("f_py", &f_py)],
172 );
173
174 let roundtripped_f: $wrapper<$float_type> = f_py.extract().unwrap();
175
176 assert_eq!(f, roundtripped_f);
177 })
178 }
179
180 #[test]
181 fn $infinity_test() {
182 let inner_pinf = <$float_type>::INFINITY;
183 let pinf = $constructor(inner_pinf);
184
185 let inner_ninf = <$float_type>::NEG_INFINITY;
186 let ninf = $constructor(inner_ninf);
187
188 Python::attach(|py| {
189 let pinf_py: Bound<'_, PyFloat> = pinf.into_pyobject(py).unwrap();
190 let ninf_py: Bound<'_, PyFloat> = ninf.into_pyobject(py).unwrap();
191
192 py_run(
193 py,
194 c"\
195 assert pinf_py == float('inf')\n\
196 assert ninf_py == float('-inf')",
197 [("pinf_py", &pinf_py), ("ninf_py", &ninf_py)],
198 );
199
200 let roundtripped_pinf: $wrapper<$float_type> = pinf_py.extract().unwrap();
201 let roundtripped_ninf: $wrapper<$float_type> = ninf_py.extract().unwrap();
202
203 assert_eq!(pinf, roundtripped_pinf);
204 assert_eq!(ninf, roundtripped_ninf);
205 })
206 }
207
208 #[test]
209 fn $zero_test() {
210 let inner_pzero: $float_type = 0.0;
211 let pzero = $constructor(inner_pzero);
212
213 let inner_nzero: $float_type = -0.0;
214 let nzero = $constructor(inner_nzero);
215
216 Python::attach(|py| {
217 let pzero_py: Bound<'_, PyFloat> = pzero.into_pyobject(py).unwrap();
218 let nzero_py: Bound<'_, PyFloat> = nzero.into_pyobject(py).unwrap();
219
220 py_run(
223 py,
224 c"\
225 import math\n\
226 assert pzero_py == 0.0\n\
227 assert math.copysign(1.0, pzero_py) > 0.0\n\
228 assert nzero_py == 0.0\n\
229 assert math.copysign(1.0, nzero_py) < 0.0",
230 [("pzero_py", &pzero_py), ("nzero_py", &nzero_py)],
231 );
232
233 let roundtripped_pzero: $wrapper<$float_type> = pzero_py.extract().unwrap();
234 let roundtripped_nzero: $wrapper<$float_type> = nzero_py.extract().unwrap();
235
236 assert_eq!(pzero, roundtripped_pzero);
237 assert_eq!(roundtripped_pzero.signum(), 1.0);
238 assert_eq!(nzero, roundtripped_nzero);
239 assert_eq!(roundtripped_nzero.signum(), -1.0);
240 })
241 }
242 };
243 }
244 float_roundtrip_tests!(
245 OrderedFloat,
246 f32,
247 OrderedFloat,
248 ordered_float_f32_standard,
249 ordered_float_f32_wasm,
250 ordered_float_f32_infinity,
251 ordered_float_f32_zero
252 );
253 float_roundtrip_tests!(
254 OrderedFloat,
255 f64,
256 OrderedFloat,
257 ordered_float_f64_standard,
258 ordered_float_f64_wasm,
259 ordered_float_f64_infinity,
260 ordered_float_f64_zero
261 );
262 float_roundtrip_tests!(
263 NotNan,
264 f32,
265 |val| NotNan::new(val).unwrap(),
266 not_nan_f32_standard,
267 not_nan_f32_wasm,
268 not_nan_f32_infinity,
269 not_nan_f32_zero
270 );
271 float_roundtrip_tests!(
272 NotNan,
273 f64,
274 |val| NotNan::new(val).unwrap(),
275 not_nan_f64_standard,
276 not_nan_f64_wasm,
277 not_nan_f64_infinity,
278 not_nan_f64_zero
279 );
280
281 macro_rules! ordered_float_pynan_tests {
282 ($test_name:ident, $float_type:ty) => {
283 #[test]
284 fn $test_name() {
285 let inner_nan: $float_type = <$float_type>::NAN;
286 let nan = OrderedFloat(inner_nan);
287
288 Python::attach(|py| {
289 let nan_py: Bound<'_, PyFloat> = nan.into_pyobject(py).unwrap();
290
291 py_run(
292 py,
293 c"import math\nassert math.isnan(nan_py)",
294 [("nan_py", &nan_py)],
295 );
296
297 let roundtripped_nan: OrderedFloat<$float_type> = nan_py.extract().unwrap();
298
299 assert_eq!(nan, roundtripped_nan);
300 })
301 }
302 };
303 }
304 ordered_float_pynan_tests!(test_ordered_float_pynan_f32, f32);
305 ordered_float_pynan_tests!(test_ordered_float_pynan_f64, f64);
306
307 macro_rules! not_nan_pynan_tests {
308 ($test_name:ident, $float_type:ty) => {
309 #[test]
310 fn $test_name() {
311 Python::attach(|py| {
312 let nan_py = py.eval(c"float('nan')", None, None).unwrap();
313
314 let nan_rs: Result<NotNan<$float_type>, _> = nan_py.extract();
315
316 assert!(nan_rs.is_err());
317 })
318 }
319 };
320 }
321 not_nan_pynan_tests!(test_not_nan_pynan_f32, f32);
322 not_nan_pynan_tests!(test_not_nan_pynan_f64, f64);
323
324 macro_rules! py64_rs32 {
325 ($test_name:ident, $wrapper:ident, $float_type:ty) => {
326 #[test]
327 fn $test_name() {
328 Python::attach(|py| {
329 let py_64 = py
330 .import("sys")
331 .unwrap()
332 .getattr("float_info")
333 .unwrap()
334 .getattr("max")
335 .unwrap();
336 let rs_32 = py_64.extract::<$wrapper<f32>>().unwrap();
337 assert!(rs_32.is_infinite());
339 })
340 }
341 };
342 }
343 py64_rs32!(ordered_float_f32, OrderedFloat, f32);
344 py64_rs32!(ordered_float_f64, OrderedFloat, f64);
345 py64_rs32!(not_nan_f32, NotNan, f32);
346 py64_rs32!(not_nan_f64, NotNan, f64);
347}