1use oxinum_core::Sign;
18
19use super::float::{BigFloat, FloatClass};
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum BinOp {
24 Add,
25 Sub,
26 Mul,
27 Div,
28 Rem,
29}
30
31pub fn nonfinite_binop(lhs: &BigFloat, rhs: &BigFloat, op: BinOp) -> Option<BigFloat> {
44 let prec = lhs.precision.max(rhs.precision);
45
46 if lhs.is_nan() || rhs.is_nan() {
48 return Some(BigFloat::nan(prec));
49 }
50
51 match op {
52 BinOp::Add => nonfinite_add(lhs, rhs, prec),
53 BinOp::Sub => {
54 let neg_rhs = rhs.neg();
56 nonfinite_add(lhs, &neg_rhs, prec)
57 }
58 BinOp::Mul => nonfinite_mul(lhs, rhs, prec),
59 BinOp::Div => nonfinite_div(lhs, rhs, prec, true),
60 BinOp::Rem => nonfinite_rem(lhs, rhs, prec),
61 }
62}
63
64pub fn nonfinite_propagate(lhs: &BigFloat, rhs: &BigFloat, op: BinOp) -> Option<BigFloat> {
69 let prec = lhs.precision.max(rhs.precision);
70
71 if lhs.is_nan() || rhs.is_nan() {
72 return Some(BigFloat::nan(prec));
73 }
74
75 if lhs.is_finite() && rhs.is_finite() {
76 return None; }
78
79 match op {
82 BinOp::Add => nonfinite_add(lhs, rhs, prec),
83 BinOp::Sub => {
84 let neg_rhs = rhs.neg();
85 nonfinite_add(lhs, &neg_rhs, prec)
86 }
87 BinOp::Mul => nonfinite_mul(lhs, rhs, prec),
88 BinOp::Div => nonfinite_div(lhs, rhs, prec, false),
89 BinOp::Rem => nonfinite_rem(lhs, rhs, prec),
90 }
91}
92
93fn nonfinite_add(lhs: &BigFloat, rhs: &BigFloat, prec: u32) -> Option<BigFloat> {
98 match (lhs.class, rhs.class) {
99 (FloatClass::Infinite, FloatClass::Infinite) => {
101 if lhs.sign == rhs.sign {
102 Some(signed_inf(lhs.sign, prec))
103 } else {
104 Some(BigFloat::nan(prec))
105 }
106 }
107 (FloatClass::Infinite, FloatClass::Finite) => Some(signed_inf(lhs.sign, prec)),
109 (FloatClass::Finite, FloatClass::Infinite) => Some(signed_inf(rhs.sign, prec)),
110 (FloatClass::Finite, FloatClass::Finite) => None,
112 _ => unreachable!("NaN should have been handled before reaching nonfinite_add"),
114 }
115}
116
117fn nonfinite_mul(lhs: &BigFloat, rhs: &BigFloat, prec: u32) -> Option<BigFloat> {
118 match (lhs.class, rhs.class) {
119 (FloatClass::Infinite, FloatClass::Finite) if rhs.is_zero() => Some(BigFloat::nan(prec)),
121 (FloatClass::Finite, FloatClass::Infinite) if lhs.is_zero() => Some(BigFloat::nan(prec)),
122 (FloatClass::Infinite, FloatClass::Infinite)
124 | (FloatClass::Infinite, FloatClass::Finite)
125 | (FloatClass::Finite, FloatClass::Infinite) => {
126 let sign = xor_sign(lhs.sign, rhs.sign);
127 Some(signed_inf(sign, prec))
128 }
129 (FloatClass::Finite, FloatClass::Finite) => None,
131 _ => unreachable!("NaN should have been handled before reaching nonfinite_mul"),
132 }
133}
134
135fn nonfinite_div(
136 lhs: &BigFloat,
137 rhs: &BigFloat,
138 prec: u32,
139 generate_from_finite: bool,
140) -> Option<BigFloat> {
141 match (lhs.class, rhs.class) {
142 (FloatClass::Infinite, FloatClass::Infinite) => Some(BigFloat::nan(prec)),
144 (FloatClass::Infinite, FloatClass::Finite) => {
146 if rhs.is_zero() {
148 Some(BigFloat::nan(prec))
149 } else {
150 Some(signed_inf(xor_sign(lhs.sign, rhs.sign), prec))
151 }
152 }
153 (FloatClass::Finite, FloatClass::Infinite) => Some(BigFloat::zero(prec)),
155 (FloatClass::Finite, FloatClass::Finite) => {
157 if generate_from_finite && rhs.is_zero() {
158 if lhs.is_zero() {
160 Some(BigFloat::nan(prec))
162 } else {
163 Some(signed_inf(lhs.sign, prec))
164 }
165 } else {
166 None }
168 }
169 _ => unreachable!("NaN should have been handled before reaching nonfinite_div"),
170 }
171}
172
173fn nonfinite_rem(lhs: &BigFloat, rhs: &BigFloat, prec: u32) -> Option<BigFloat> {
174 match (lhs.class, rhs.class) {
175 (FloatClass::Infinite, _) => Some(BigFloat::nan(prec)),
177 (FloatClass::Finite, FloatClass::Finite) if rhs.is_zero() => Some(BigFloat::nan(prec)),
179 (FloatClass::Finite, FloatClass::Infinite) => None,
183 (FloatClass::Finite, FloatClass::Finite) => None,
185 _ => unreachable!("NaN should have been handled before reaching nonfinite_rem"),
186 }
187}
188
189fn xor_sign(a: Sign, b: Sign) -> Sign {
194 if a == b {
195 Sign::Positive
196 } else {
197 Sign::Negative
198 }
199}
200
201fn signed_inf(sign: Sign, prec: u32) -> BigFloat {
202 if sign == Sign::Negative {
203 BigFloat::neg_infinity(prec)
204 } else {
205 BigFloat::infinity(prec)
206 }
207}
208
209#[cfg(test)]
215mod tests {
216 use super::*;
217 use crate::native::{BigFloat, RoundingMode};
218
219 const P: u32 = 53;
220
221 fn finite(v: i64) -> BigFloat {
222 BigFloat::from_i64(v, P, RoundingMode::HalfEven)
223 }
224
225 #[test]
230 fn add_nan_propagates() {
231 let nan = BigFloat::nan(P);
232 let fin = finite(1);
233 let r = nonfinite_binop(&nan, &fin, BinOp::Add);
234 assert!(r.expect("should produce NaN").is_nan());
235 let r2 = nonfinite_binop(&fin, &nan, BinOp::Add);
236 assert!(r2.expect("should produce NaN").is_nan());
237 }
238
239 #[test]
240 fn add_inf_plus_inf_same_sign() {
241 let pos_inf = BigFloat::infinity(P);
242 let r =
243 nonfinite_binop(&pos_inf, &pos_inf, BinOp::Add).expect("+inf + +inf should be +inf");
244 assert!(r.is_infinite());
245 assert!(r.is_sign_positive());
246 }
247
248 #[test]
249 fn add_inf_plus_neg_inf_is_nan() {
250 let pos_inf = BigFloat::infinity(P);
251 let neg_inf = BigFloat::neg_infinity(P);
252 let r = nonfinite_binop(&pos_inf, &neg_inf, BinOp::Add).expect("+inf + -inf should be NaN");
253 assert!(r.is_nan());
254 }
255
256 #[test]
257 fn add_inf_plus_finite() {
258 let pos_inf = BigFloat::infinity(P);
259 let fin = finite(42);
260 let r = nonfinite_binop(&pos_inf, &fin, BinOp::Add).expect("+inf + finite should be +inf");
261 assert!(r.is_infinite() && r.is_sign_positive());
262 }
263
264 #[test]
265 fn add_both_finite_returns_none() {
266 let a = finite(1);
267 let b = finite(2);
268 assert!(nonfinite_binop(&a, &b, BinOp::Add).is_none());
269 }
270
271 #[test]
276 fn sub_inf_minus_inf_same_sign_is_nan() {
277 let pos_inf = BigFloat::infinity(P);
278 let r = nonfinite_binop(&pos_inf, &pos_inf, BinOp::Sub).expect("+inf - +inf should be NaN");
279 assert!(r.is_nan());
280 }
281
282 #[test]
287 fn mul_inf_times_zero_is_nan() {
288 let pos_inf = BigFloat::infinity(P);
289 let zero = BigFloat::zero(P);
290 let r = nonfinite_binop(&pos_inf, &zero, BinOp::Mul).expect("inf * 0 should be NaN");
291 assert!(r.is_nan());
292 }
293
294 #[test]
295 fn mul_inf_times_nonzero_finite() {
296 let pos_inf = BigFloat::infinity(P);
297 let fin = finite(3);
298 let r = nonfinite_binop(&pos_inf, &fin, BinOp::Mul).expect("+inf * 3 should be +inf");
299 assert!(r.is_infinite() && r.is_sign_positive());
300 }
301
302 #[test]
303 fn mul_pos_inf_times_neg_finite() {
304 let pos_inf = BigFloat::infinity(P);
305 let neg = finite(-3);
306 let r = nonfinite_binop(&pos_inf, &neg, BinOp::Mul).expect("+inf * -3 should be -inf");
307 assert!(r.is_infinite() && r.is_sign_negative());
308 }
309
310 #[test]
311 fn mul_inf_times_inf() {
312 let pos_inf = BigFloat::infinity(P);
313 let neg_inf = BigFloat::neg_infinity(P);
314 let r =
315 nonfinite_binop(&pos_inf, &neg_inf, BinOp::Mul).expect("+inf * -inf should be -inf");
316 assert!(r.is_infinite() && r.is_sign_negative());
317 }
318
319 #[test]
324 fn div_inf_over_inf_is_nan() {
325 let pos_inf = BigFloat::infinity(P);
326 let r = nonfinite_binop(&pos_inf, &pos_inf, BinOp::Div).expect("inf/inf should be NaN");
327 assert!(r.is_nan());
328 }
329
330 #[test]
331 fn div_finite_over_zero() {
332 let fin = finite(5);
333 let zero = BigFloat::zero(P);
334 let r = nonfinite_binop(&fin, &zero, BinOp::Div).expect("5/0 should be +inf");
335 assert!(r.is_infinite() && r.is_sign_positive());
336 }
337
338 #[test]
339 fn div_zero_over_zero_is_nan() {
340 let zero = BigFloat::zero(P);
341 let r = nonfinite_binop(&zero, &zero, BinOp::Div).expect("0/0 should be NaN");
342 assert!(r.is_nan());
343 }
344
345 #[test]
346 fn div_finite_over_inf_is_zero() {
347 let fin = finite(7);
348 let pos_inf = BigFloat::infinity(P);
349 let r = nonfinite_binop(&fin, &pos_inf, BinOp::Div).expect("7/inf should be zero");
350 assert!(r.is_zero());
351 }
352
353 #[test]
358 fn rem_inf_mod_anything_is_nan() {
359 let pos_inf = BigFloat::infinity(P);
360 let fin = finite(3);
361 let r = nonfinite_binop(&pos_inf, &fin, BinOp::Rem).expect("inf % 3 should be NaN");
362 assert!(r.is_nan());
363 }
364
365 #[test]
366 fn rem_finite_mod_zero_is_nan() {
367 let fin = finite(5);
368 let zero = BigFloat::zero(P);
369 let r = nonfinite_binop(&fin, &zero, BinOp::Rem).expect("5 % 0 should be NaN");
370 assert!(r.is_nan());
371 }
372
373 #[test]
374 fn rem_finite_mod_inf_returns_none() {
375 let fin = finite(5);
377 let pos_inf = BigFloat::infinity(P);
378 let r = nonfinite_binop(&fin, &pos_inf, BinOp::Rem);
379 assert!(
380 r.is_none(),
381 "finite % inf should return None (caller returns lhs)"
382 );
383 }
384
385 #[test]
390 fn propagate_both_finite_returns_none() {
391 let a = finite(1);
392 let b = finite(2);
393 assert!(nonfinite_propagate(&a, &b, BinOp::Add).is_none());
394 }
395
396 #[test]
397 fn propagate_nan_returns_nan() {
398 let nan = BigFloat::nan(P);
399 let fin = finite(1);
400 assert!(nonfinite_propagate(&nan, &fin, BinOp::Add)
401 .expect("should be NaN")
402 .is_nan());
403 }
404
405 #[test]
406 fn propagate_div_finite_over_zero_returns_none() {
407 let fin = finite(5);
410 let zero = BigFloat::zero(P);
411 assert!(nonfinite_propagate(&fin, &zero, BinOp::Div).is_none());
412 }
413}