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