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