1use binius_core::Word;
3use binius_frontend::{CircuitBuilder, Wire};
4
5use super::utils::*;
6
7pub fn float64_add(builder: &CircuitBuilder, a: Wire, b: Wire) -> Wire {
17 let pa = fp64_unpack(builder, a);
19 let pb = fp64_unpack(builder, b);
20
21 let (sig_a, exp_a) = fp64_ext_sig_and_exp(builder, &pa);
23 let (sig_b, exp_b) = fp64_ext_sig_and_exp(builder, &pb);
24
25 let (
27 sig_a_ordered,
28 exp_a_ordered,
29 sign_a_ordered,
30 sig_b_ordered,
31 exp_b_ordered,
32 sign_b_ordered,
33 _swapped,
34 ) = fp64_order_by_exp(builder, sig_a, exp_a, pa.sign, sig_b, exp_b, pb.sign);
35
36 let d = isub(builder, exp_a_ordered, exp_b_ordered);
38 let s_b = fp64_align_with_sticky(builder, sig_b_ordered, d);
39
40 let diff_sign = builder.bxor(sign_a_ordered, sign_b_ordered);
42
43 let (sum_norm, exp_add) = fp64_add_path(builder, sig_a_ordered, s_b, exp_a_ordered);
45
46 let (diff_norm, exp_sub, sign_sub, mags_equal) =
48 fp64_sub_path(builder, sig_a_ordered, s_b, exp_a_ordered, sign_a_ordered, sign_b_ordered);
49
50 let (res_sig, res_exp, res_sign) = fp64_merge_and_cancel(
52 builder,
53 diff_sign,
54 sum_norm,
55 exp_add,
56 sign_a_ordered,
57 diff_norm,
58 exp_sub,
59 sign_sub,
60 mags_equal,
61 );
62
63 let (sig_round_base, exp_round_base, exp_lt_1) =
65 fp64_underflow_shift(builder, res_sig, res_exp);
66
67 let (mant_final_53, exp_after_round, mant_overflow_mask) =
69 fp64_round_rne(builder, sig_round_base, exp_round_base);
70
71 let msb_is_zero = builder.bnot(sig_round_base); let base_is_one = builder.icmp_eq(exp_round_base, one(builder));
80 let in_sub_regime = builder.bor(exp_lt_1, builder.band(base_is_one, msb_is_zero));
81 let stayed_sub_mask = builder.band(in_sub_regime, builder.bnot(mant_overflow_mask));
82 let finite_or_inf =
83 fp64_pack_finite_or_inf(builder, res_sign, mant_final_53, exp_after_round, stayed_sub_mask);
84
85 fp64_finish_specials(builder, &pa, &pb, finite_or_inf)
87}
88
89fn fp64_ext_sig_and_exp(b: &CircuitBuilder, p: &Fp64Parts) -> (Wire, Wire) {
104 let one52 = b.add_constant_64(1u64 << 52);
105
106 let sig_norm = b.shl(b.bor(one52, p.frac), 11);
107 let sig_sub = b.shl(p.frac, 11);
108 let sig = b.select(p.is_norm, sig_norm, sig_sub);
109
110 let exp_eff = b.select(p.is_norm, p.exp, one(b)); (sig, exp_eff)
112}
113
114fn fp64_order_by_exp(
123 b: &CircuitBuilder,
124 sig_a: Wire,
125 exp_a: Wire,
126 sign_a: Wire,
127 sig_b: Wire,
128 exp_b: Wire,
129 sign_b: Wire,
130) -> (Wire, Wire, Wire, Wire, Wire, Wire, Wire) {
131 let a_lt_b = b.icmp_ult(exp_a, exp_b);
132
133 let exp_a_ordered = b.select(a_lt_b, exp_b, exp_a);
134 let exp_b_ordered = b.select(a_lt_b, exp_a, exp_b);
135 let sig_a_ordered = b.select(a_lt_b, sig_b, sig_a);
136 let sig_b_ordered = b.select(a_lt_b, sig_a, sig_b);
137 let sign_a_ordered = b.select(a_lt_b, sign_b, sign_a);
138 let sign_b_ordered = b.select(a_lt_b, sign_a, sign_b);
139
140 (
141 sig_a_ordered,
142 exp_a_ordered,
143 sign_a_ordered,
144 sig_b_ordered,
145 exp_b_ordered,
146 sign_b_ordered,
147 a_lt_b,
148 )
149}
150
151fn fp64_align_with_sticky(b: &CircuitBuilder, sig_b: Wire, d: Wire) -> Wire {
161 let (mut v, sticky) = var_shr_with_sticky(b, sig_b, d, true);
162
163 v = b.bor(v, b.band(sticky, one(b)));
166 v
167}
168
169fn fp64_add_path(b: &CircuitBuilder, sig_a: Wire, s_b: Wire, exp_a: Wire) -> (Wire, Wire) {
178 let (sum_raw, carry_mask) = b.iadd(sig_a, s_b);
179
180 let carry_bit01 = bit_lsb(b, carry_mask, 63); let carry_sel_mask = bit_msb01(b, carry_mask, 63); let sum_shift1 = b.shr(sum_raw, 1);
186 let old_r = bit_lsb(b, sum_raw, 9);
187 let old_s = b.band(sum_raw, one(b));
188 let new_s = b.bor(old_r, old_s);
189
190 let carry_hi = b.shl(carry_bit01, 63);
192 let sum_shift1_with_carry = b.bor(sum_shift1, carry_hi);
193
194 let keep = b.bnot(one(b));
195 let sum_shift1clr = b.band(sum_shift1_with_carry, keep);
196 let sum_norm = b.select(carry_sel_mask, b.bor(sum_shift1clr, new_s), sum_raw);
197
198 let exp_add = b.iadd(exp_a, carry_bit01).0;
200 (sum_norm, exp_add)
201}
202
203fn fp64_sub_path(
216 b: &CircuitBuilder,
217 sig_a: Wire,
218 s_b: Wire,
219 exp_a: Wire,
220 sign_a: Wire,
221 sign_b: Wire,
222) -> (Wire, Wire, Wire, Wire) {
223 let a_lt_b = b.icmp_ult(sig_a, s_b);
224 let big = b.select(a_lt_b, s_b, sig_a);
225 let small = b.select(a_lt_b, sig_a, s_b);
226 let diff_raw = isub(b, big, small);
227 let sign_sub = b.select(a_lt_b, sign_b, sign_a);
228 let mags_eq = b.icmp_eq(sig_a, s_b);
229
230 let lz = clz64(b, diff_raw);
231 let c64 = b.add_constant_64(64);
232 let c63 = b.add_constant_64(63);
233 let lt64 = b.icmp_ult(lz, c64);
234 let lz_clamped = b.select(lt64, lz, c63);
235
236 let exp_a_m1 = isub(b, exp_a, one(b));
237 let lz_lt_expm1 = b.icmp_ult(lz_clamped, exp_a_m1);
238 let sh = b.select(lz_lt_expm1, lz_clamped, exp_a_m1);
239
240 let diff_norm = var_shl(b, diff_raw, sh);
241 let exp_sub = isub(b, exp_a, sh);
242
243 (diff_norm, exp_sub, sign_sub, mags_eq)
244}
245
246#[allow(clippy::too_many_arguments)]
256fn fp64_merge_and_cancel(
257 b: &CircuitBuilder,
258 diff_sign: Wire,
259 sum_norm: Wire,
260 exp_add: Wire,
261 sign_a: Wire,
262 diff_norm: Wire,
263 exp_sub: Wire,
264 sign_sub: Wire,
265 mags_equal: Wire,
266) -> (Wire, Wire, Wire) {
267 let res_sig_pre = b.select(diff_sign, diff_norm, sum_norm);
268 let res_exp_pre = b.select(diff_sign, exp_sub, exp_add);
269 let res_sign_pre = b.select(diff_sign, sign_sub, sign_a);
270
271 let cancel = b.band(diff_sign, mags_equal);
273
274 let res_sig = b.select(cancel, zero(b), res_sig_pre);
275 let res_exp = b.select(cancel, zero(b), res_exp_pre); let res_sign = b.select(cancel, zero(b), res_sign_pre); (res_sig, res_exp, res_sign)
278}
279
280fn fp64_finish_specials(
295 b: &CircuitBuilder,
296 pa: &Fp64Parts,
297 pb: &Fp64Parts,
298 finite_or_inf: Wire,
299) -> Wire {
300 let qnan = b.add_constant_64(0x7FF8_0000_0000_0000);
301 let exp_2047 = b.add_constant_64(0x7FF);
302 let inf_payload = b.shl(exp_2047, 52);
303
304 let diff_sign = b.bxor(pa.sign, pb.sign);
305 let opp_inf_nan = b.band(b.band(pa.is_inf, pb.is_inf), diff_sign);
306 let any_nan = b.bor(pa.is_nan, pb.is_nan);
307 let nan_mask = b.bor(any_nan, opp_inf_nan);
308
309 let any_inf = b.bor(pa.is_inf, pb.is_inf);
311 let inf_sign = b.select(pb.is_inf, pb.sign, pa.sign);
312 let packed_inf = b.bor(b.band(inf_sign, b.add_constant(Word::MSB_ONE)), inf_payload);
313
314 let with_inf = b.select(any_inf, packed_inf, finite_or_inf);
315 b.select(nan_mask, qnan, with_inf)
316}
317
318#[cfg(test)]
319mod tests {
320 use binius_core::word::Word;
321
322 use super::*;
323 use crate::float64::utils::tests::{
324 f64_bits_semantic_eq, ref_fp64_pack_finite_or_inf, ref_fp64_round_rne,
325 ref_fp64_underflow_shift, ref_fp64_unpack,
326 };
327
328 fn ref_fp64_ext_sig_and_exp(_sign: u64, exp: u64, frac: u64, is_norm: u64) -> (u64, u64) {
329 let sig = if is_norm != 0 {
330 ((1u64 << 52) | frac) << 11
331 } else {
332 frac << 11
333 };
334
335 let exp_eff = if is_norm != 0 { exp } else { 1 };
336
337 (sig, exp_eff)
338 }
339
340 fn ref_fp64_order_by_exp(
341 sig_a: u64,
342 exp_a: u64,
343 sign_a: u64,
344 sig_b: u64,
345 exp_b: u64,
346 sign_b: u64,
347 ) -> (u64, u64, u64, u64, u64, u64, u64) {
348 if exp_a < exp_b {
349 (sig_b, exp_b, sign_b, sig_a, exp_a, sign_a, 1u64 << 63)
350 } else {
351 (sig_a, exp_a, sign_a, sig_b, exp_b, sign_b, 0)
352 }
353 }
354
355 fn ref_fp64_align_with_sticky(sig_b: u64, d: u64) -> u64 {
356 let d_eff = std::cmp::min(d, 63);
357 if d_eff == 0 {
358 return sig_b;
359 }
360
361 let shifted = sig_b >> d_eff;
362 let lost_bits = sig_b & ((1u64 << d_eff) - 1);
363 let sticky = if lost_bits != 0 { 1 } else { 0 };
364
365 let old_bit0 = shifted & 1;
367 let new_bit0 = old_bit0 | sticky;
368 (shifted & !1) | new_bit0
369 }
370
371 fn ref_fp64_add_path(sig_a: u64, s_b: u64, exp_a: u64) -> (u64, u64) {
372 let sum_raw = sig_a.wrapping_add(s_b);
377
378 let carry01 = if sum_raw < sig_a { 1 } else { 0 };
381
382 if carry01 == 1 {
384 let old_r = (sum_raw >> 9) & 1; let old_s = sum_raw & 1; let shifted = (sum_raw >> 1) | (1u64 << 63);
390 let new_s = old_r | old_s; let sum_norm = (shifted & !1) | new_s; let exp_add = exp_a + 1;
394
395 (sum_norm, exp_add)
396 } else {
397 let sum_norm = sum_raw;
399 let exp_add = exp_a;
400
401 (sum_norm, exp_add)
402 }
403 }
404
405 fn ref_fp64_sub_path(
406 sig_a: u64,
407 s_b: u64,
408 exp_a: u64,
409 sign_a: u64,
410 sign_b: u64,
411 ) -> (u64, u64, u64, u64) {
412 let a_lt_b = if sig_a < s_b { 1u64 << 63 } else { 0 };
414 let big = if a_lt_b != 0 { s_b } else { sig_a };
415 let small = if a_lt_b != 0 { sig_a } else { s_b };
416 let diff_raw = big.wrapping_sub(small);
417 let sign_sub = if a_lt_b != 0 { sign_b } else { sign_a };
418 let mags_eq = if sig_a == s_b { 1u64 << 63 } else { 0 };
419
420 let lz = if diff_raw == 0 {
422 64
423 } else {
424 diff_raw.leading_zeros() as u64
425 };
426 let c64 = 64;
427 let c63 = 63;
428 let lt64 = if lz < c64 { 1u64 << 63 } else { 0 };
429 let lz_clamped = if lt64 != 0 { lz } else { c63 };
430
431 let exp_a_m1 = exp_a.wrapping_sub(1);
432 let lz_lt_expm1 = if lz_clamped < exp_a_m1 { 1u64 << 63 } else { 0 };
433 let sh = if lz_lt_expm1 != 0 {
434 lz_clamped
435 } else {
436 exp_a_m1
437 };
438
439 let shift_amt = std::cmp::min(sh, 63); let diff_norm = diff_raw << shift_amt;
442 let exp_sub = exp_a.wrapping_sub(sh);
443
444 (diff_norm, exp_sub, sign_sub, mags_eq)
445 }
446
447 #[allow(clippy::too_many_arguments)]
448 fn ref_fp64_merge_and_cancel(
449 diff_sign: u64,
450 sum_norm: u64,
451 exp_add: u64,
452 sign_a: u64,
453 diff_norm: u64,
454 exp_sub: u64,
455 sign_sub: u64,
456 mags_equal: u64,
457 ) -> (u64, u64, u64) {
458 let (res_sig_pre, res_exp_pre, res_sign_pre) = if diff_sign & (1u64 << 63) == 0 {
459 (sum_norm, exp_add, sign_a)
460 } else {
461 (diff_norm, exp_sub, sign_sub)
462 };
463
464 let cancel = if diff_sign & (1u64 << 63) != 0 && mags_equal != 0 {
466 1u64 << 63
467 } else {
468 0
469 };
470
471 let res_sig = if cancel != 0 { 0 } else { res_sig_pre };
472 let res_exp = if cancel != 0 { 0 } else { res_exp_pre };
473 let res_sign = if cancel != 0 { 0 } else { res_sign_pre };
474
475 (res_sig, res_exp, res_sign)
476 }
477
478 fn ref_fp64_finish_specials(
479 pa_is_nan: u64,
480 pa_is_inf: u64,
481 pa_sign_msb: u64,
482 pb_is_nan: u64,
483 pb_is_inf: u64,
484 pb_sign_msb: u64,
485 finite_or_inf: u64,
486 ) -> u64 {
487 let qnan = 0x7FF8_0000_0000_0000u64;
488
489 let any_nan = (pa_is_nan | pb_is_nan) != 0;
490 let same_sign = pa_sign_msb == pb_sign_msb;
491 let opp_inf_nan = (pa_is_inf != 0) && (pb_is_inf != 0) && !same_sign;
492 let nan_case = any_nan || opp_inf_nan;
493
494 if nan_case {
495 return qnan;
496 }
497
498 let any_inf = (pa_is_inf | pb_is_inf) != 0;
499 if any_inf {
500 let inf_msb = if pb_is_inf != 0 {
501 pb_sign_msb
502 } else {
503 pa_sign_msb
504 } & (1u64 << 63);
505 let inf_payload = 0x7FFu64 << 52;
506 return inf_msb | inf_payload;
507 }
508
509 finite_or_inf
510 }
511
512 fn ref_float64_add(a_bits: u64, b_bits: u64) -> u64 {
513 let pa = ref_fp64_unpack(a_bits);
515 let pb = ref_fp64_unpack(b_bits);
516
517 let (sig_a0, exp_a0) = ref_fp64_ext_sig_and_exp(pa.sign, pa.exp, pa.frac, pa.is_norm);
519 let (sig_b0, exp_b0) = ref_fp64_ext_sig_and_exp(pb.sign, pb.exp, pb.frac, pb.is_norm);
520
521 let (sig_a, exp_a, sign_a, sig_b, exp_b, sign_b, _swapped) =
523 ref_fp64_order_by_exp(sig_a0, exp_a0, pa.sign, sig_b0, exp_b0, pb.sign);
524
525 let d = exp_a.wrapping_sub(exp_b);
527 let s_b_align = ref_fp64_align_with_sticky(sig_b, d);
528
529 let diff_sign = sign_a ^ sign_b;
531 let (sum_norm, exp_add) = ref_fp64_add_path(sig_a, s_b_align, exp_a);
532 let (diff_norm, exp_sub, sign_sub, mags_equal) =
533 ref_fp64_sub_path(sig_a, s_b_align, exp_a, sign_a, sign_b);
534
535 let (res_sig, res_exp, res_sign) = ref_fp64_merge_and_cancel(
537 diff_sign, sum_norm, exp_add, sign_a, diff_norm, exp_sub, sign_sub, mags_equal,
538 );
539
540 let (sig_round_base, exp_round_base, exp_lt_1) = ref_fp64_underflow_shift(res_sig, res_exp);
542
543 let (mant_final_53, exp_after_round, mant_overflow_mask) =
545 ref_fp64_round_rne(sig_round_base, exp_round_base);
546
547 let msb = (sig_round_base >> 63) & 1;
552 let base_is_one = exp_round_base == 1;
553 let in_sub_regime = (exp_lt_1 != 0) || (base_is_one && msb == 0);
554 let stayed_sub_mask = if in_sub_regime && mant_overflow_mask == 0 {
555 1u64 << 63
556 } else {
557 0
558 };
559 let finite_or_inf =
560 ref_fp64_pack_finite_or_inf(res_sign, mant_final_53, exp_after_round, stayed_sub_mask);
561
562 ref_fp64_finish_specials(
564 pa.is_nan,
565 pa.is_inf,
566 pa.sign,
567 pb.is_nan,
568 pb.is_inf,
569 pb.sign,
570 finite_or_inf,
571 )
572 }
573
574 #[test]
575 fn test_fp64_align_with_sticky() {
576 let test_cases = [
577 (0x8000000000000000u64, 1), (0x8000000000000001u64, 1), (0xFFFFFFFFFFFFFFFFu64, 4), (0x1000000000000000u64, 0), (0x123456789ABCDEFFu64, 8), ];
584
585 for (sig_b, d) in test_cases {
586 let builder = CircuitBuilder::new();
587 let sig_b_wire = builder.add_inout();
588 let d_wire = builder.add_inout();
589 let result = fp64_align_with_sticky(&builder, sig_b_wire, d_wire);
590 let expected_wire = builder.add_inout();
591 builder.assert_eq("align_result", result, expected_wire);
592
593 let circuit = builder.build();
594 let mut w = circuit.new_witness_filler();
595 w[sig_b_wire] = Word(sig_b);
596 w[d_wire] = Word(d);
597 w[expected_wire] = Word(ref_fp64_align_with_sticky(sig_b, d));
598
599 circuit.populate_wire_witness(&mut w).unwrap();
600 let cs = circuit.constraint_system();
601 cs.verify(&w.into_value_vec()).unwrap();
602 }
603 }
604
605 #[test]
606 fn test_iadd_simple() {
607 let builder = CircuitBuilder::new();
608 let a = builder.add_inout();
609 let b = builder.add_inout();
610 let (result, _) = builder.iadd(a, b);
611 let expected = builder.add_inout();
612 builder.assert_eq("iadd_result", result, expected);
613
614 let circuit = builder.build();
615 let mut w = circuit.new_witness_filler();
616 w[a] = Word(1000);
617 w[b] = Word(1);
618 w[expected] = Word(1001);
619
620 circuit.populate_wire_witness(&mut w).unwrap();
621 let cs = circuit.constraint_system();
622 cs.verify(&w.into_value_vec()).unwrap();
623 }
624
625 #[test]
626 fn test_fp64_add_path() {
627 let test_cases = [
628 (0x8000000000000000u64, 0x4000000000000000u64, 1023), (0xFFFFFFFFFFFFFFFFu64, 0x0000000000000001u64, 1000), (0x1000000000000000u64, 0x1000000000000000u64, 500), (0xC000000000000000u64, 0xC000000000000000u64, 1023),
634 ];
635
636 for (sig_a, s_b, exp_a) in test_cases.iter() {
637 let builder = CircuitBuilder::new();
638 let sig_a_wire = builder.add_inout();
639 let s_b_wire = builder.add_inout();
640 let exp_a_wire = builder.add_inout();
641 let (sum_norm, exp_add) = fp64_add_path(&builder, sig_a_wire, s_b_wire, exp_a_wire);
642 let expected_sum = builder.add_inout();
643 let expected_exp = builder.add_inout();
644 builder.assert_eq("sum_norm", sum_norm, expected_sum);
645 builder.assert_eq("exp_add", exp_add, expected_exp);
646
647 let circuit = builder.build();
648 let mut w = circuit.new_witness_filler();
649 w[sig_a_wire] = Word(*sig_a);
650 w[s_b_wire] = Word(*s_b);
651 w[exp_a_wire] = Word(*exp_a);
652
653 let (ref_sum, ref_exp) = ref_fp64_add_path(*sig_a, *s_b, *exp_a);
654 w[expected_sum] = Word(ref_sum);
655 w[expected_exp] = Word(ref_exp);
656
657 circuit.populate_wire_witness(&mut w).unwrap();
658 let cs = circuit.constraint_system();
659 cs.verify(&w.into_value_vec()).unwrap();
660 }
661 }
662
663 #[test]
664 fn test_fp64_sub_path() {
665 let test_cases = [
666 (0x8000000000000000u64, 0x4000000000000000u64, 1023, 0, 1), (0x4000000000000000u64, 0x8000000000000000u64, 1020, 0, 1), (0x8000000000000000u64, 0x8000000000000000u64, 1023, 0, 1), (0x1000000000000000u64, 0x0800000000000000u64, 1000, 1, 0), ];
672
673 for (sig_a, s_b, exp_a, sign_a, sign_b) in test_cases {
674 let builder = CircuitBuilder::new();
675 let sig_a_wire = builder.add_inout();
676 let s_b_wire = builder.add_inout();
677 let exp_a_wire = builder.add_inout();
678 let sign_a_wire = builder.add_inout();
679 let sign_b_wire = builder.add_inout();
680
681 let (diff_norm, exp_sub, sign_sub, mags_equal) =
682 fp64_sub_path(&builder, sig_a_wire, s_b_wire, exp_a_wire, sign_a_wire, sign_b_wire);
683
684 let expected_diff = builder.add_inout();
685 let expected_exp = builder.add_inout();
686 let expected_sign = builder.add_inout();
687 let expected_mags = builder.add_inout();
688 let bool_mask = builder.add_constant(Word::MSB_ONE);
689
690 builder.assert_eq("diff_norm", diff_norm, expected_diff);
691 builder.assert_eq("exp_sub", exp_sub, expected_exp);
692 builder.assert_eq("sign_sub", sign_sub, expected_sign);
693 builder.assert_eq("mags_equal", builder.band(mags_equal, bool_mask), expected_mags);
694
695 let circuit = builder.build();
696 let mut w = circuit.new_witness_filler();
697 w[sig_a_wire] = Word(sig_a);
698 w[s_b_wire] = Word(s_b);
699 w[exp_a_wire] = Word(exp_a);
700 w[sign_a_wire] = Word(sign_a);
701 w[sign_b_wire] = Word(sign_b);
702
703 let (ref_diff, ref_exp, ref_sign, ref_mags) =
704 ref_fp64_sub_path(sig_a, s_b, exp_a, sign_a, sign_b);
705 w[expected_diff] = Word(ref_diff);
706 w[expected_exp] = Word(ref_exp);
707 w[expected_sign] = Word(ref_sign);
708 w[expected_mags] = Word(ref_mags);
709
710 circuit.populate_wire_witness(&mut w).unwrap();
711 let cs = circuit.constraint_system();
712 cs.verify(&w.into_value_vec()).unwrap();
713 }
714 }
715
716 #[test]
717 fn test_fp64_merge_and_cancel() {
718 let test_cases = [
719 (1u64 << 63, 0x8000000000000000u64, 1024, 0, 0x4000000000000000u64, 1020, 1, 0), (0, 0x8000000000000000u64, 1024, 0, 0x4000000000000000u64, 1020, 1, 0), (0, 0x8000000000000000u64, 1024, 0, 0x4000000000000000u64, 1020, 1, 1u64 << 63), ];
726
727 for (same_sign, sum_norm, exp_add, sign_a, diff_norm, exp_sub, sign_sub, mags_equal) in
728 test_cases
729 {
730 let builder = CircuitBuilder::new();
731 let same_sign_wire = builder.add_inout();
732 let sum_norm_wire = builder.add_inout();
733 let exp_add_wire = builder.add_inout();
734 let sign_a_wire = builder.add_inout();
735 let diff_norm_wire = builder.add_inout();
736 let exp_sub_wire = builder.add_inout();
737 let sign_sub_wire = builder.add_inout();
738 let mags_equal_wire = builder.add_inout();
739
740 let (res_sig, res_exp, res_sign) = fp64_merge_and_cancel(
741 &builder,
742 same_sign_wire,
743 sum_norm_wire,
744 exp_add_wire,
745 sign_a_wire,
746 diff_norm_wire,
747 exp_sub_wire,
748 sign_sub_wire,
749 mags_equal_wire,
750 );
751
752 let expected_sig = builder.add_inout();
753 let expected_exp = builder.add_inout();
754 let expected_sign = builder.add_inout();
755
756 builder.assert_eq("res_sig", res_sig, expected_sig);
757 builder.assert_eq("res_exp", res_exp, expected_exp);
758 builder.assert_eq("res_sign", res_sign, expected_sign);
759
760 let circuit = builder.build();
761 let mut w = circuit.new_witness_filler();
762 w[same_sign_wire] = Word(same_sign);
763 w[sum_norm_wire] = Word(sum_norm);
764 w[exp_add_wire] = Word(exp_add);
765 w[sign_a_wire] = Word(sign_a);
766 w[diff_norm_wire] = Word(diff_norm);
767 w[exp_sub_wire] = Word(exp_sub);
768 w[sign_sub_wire] = Word(sign_sub);
769 w[mags_equal_wire] = Word(mags_equal);
770
771 let (ref_sig, ref_exp, ref_sign) = ref_fp64_merge_and_cancel(
772 same_sign, sum_norm, exp_add, sign_a, diff_norm, exp_sub, sign_sub, mags_equal,
773 );
774 w[expected_sig] = Word(ref_sig);
775 w[expected_exp] = Word(ref_exp);
776 w[expected_sign] = Word(ref_sign);
777
778 circuit.populate_wire_witness(&mut w).unwrap();
779 let cs = circuit.constraint_system();
780 cs.verify(&w.into_value_vec()).unwrap();
781 }
782 }
783
784 #[test]
785 fn test_fp64_pack_finite_or_inf() {
786 let test_cases = [
787 (0, 0x1000000000000000u64, 1023, 0), (1, 0x1800000000000000u64, 1000, 0), (0, 0x0000000000001000u64, 1, 1u64 << 63), (0, 0x1000000000000000u64, 0x7FF, 0), ];
793
794 for (sign, mant_final_53, exp_after_round, stayed_sub_mask) in test_cases {
795 let builder = CircuitBuilder::new();
796 let sign_wire = builder.add_inout();
797 let mant_wire = builder.add_inout();
798 let exp_wire = builder.add_inout();
799 let stayed_sub_wire = builder.add_inout();
800
801 let sign_msb = builder.shl(sign_wire, 63);
803 let result =
804 fp64_pack_finite_or_inf(&builder, sign_msb, mant_wire, exp_wire, stayed_sub_wire);
805
806 let expected_result = builder.add_inout();
807 builder.assert_eq("pack_result", result, expected_result);
808
809 let circuit = builder.build();
810 let mut w = circuit.new_witness_filler();
811 w[sign_wire] = Word(sign);
812 w[mant_wire] = Word(mant_final_53);
813 w[exp_wire] = Word(exp_after_round);
814 w[stayed_sub_wire] = Word(stayed_sub_mask);
815
816 let ref_result = ref_fp64_pack_finite_or_inf(
817 sign << 63,
818 mant_final_53,
819 exp_after_round,
820 stayed_sub_mask,
821 );
822 w[expected_result] = Word(ref_result);
823
824 circuit.populate_wire_witness(&mut w).unwrap();
825 let cs = circuit.constraint_system();
826 cs.verify(&w.into_value_vec()).unwrap();
827 }
828 }
829
830 #[test]
831 fn test_fp64_finish_specials() {
832 let test_cases = [
833 (0, 0, 0, 0, 0, 0, 0x3FF0000000000000u64), (1u64 << 63, 0, 0, 0, 0, 0, 0x4000000000000000u64), (0, 0, 0, 1u64 << 63, 0, 1, 0x4000000000000000u64), (0, 1u64 << 63, 0, 0, 0, 1, 0x4000000000000000u64), (0, 0, 1, 0, 1u64 << 63, 1, 0x4000000000000000u64), (0, 1u64 << 63, 0, 0, 1u64 << 63, 1, 0x4000000000000000u64), ];
841
842 for (pa_is_nan, pa_is_inf, pa_sign, pb_is_nan, pb_is_inf, pb_sign, finite_or_inf) in
843 test_cases
844 {
845 let builder = CircuitBuilder::new();
846 let pa = Fp64Parts {
847 sign: builder.add_inout(),
848 exp: builder.add_inout(),
849 frac: builder.add_inout(),
850 is_nan: builder.add_inout(),
851 is_inf: builder.add_inout(),
852 is_zero: builder.add_inout(),
853 is_sub: builder.add_inout(),
854 is_norm: builder.add_inout(),
855 };
856 let pb = Fp64Parts {
857 sign: builder.add_inout(),
858 exp: builder.add_inout(),
859 frac: builder.add_inout(),
860 is_nan: builder.add_inout(),
861 is_inf: builder.add_inout(),
862 is_zero: builder.add_inout(),
863 is_sub: builder.add_inout(),
864 is_norm: builder.add_inout(),
865 };
866 let finite_or_inf_wire = builder.add_inout();
867
868 let result = fp64_finish_specials(&builder, &pa, &pb, finite_or_inf_wire);
869 let expected_result = builder.add_inout();
870 builder.assert_eq("finish_result", result, expected_result);
871
872 let circuit = builder.build();
873 let mut w = circuit.new_witness_filler();
874
875 w[pa.sign] = Word(pa_sign << 63);
878 w[pa.exp] = Word(0);
879 w[pa.frac] = Word(0);
880 w[pa.is_nan] = Word(pa_is_nan);
881 w[pa.is_inf] = Word(pa_is_inf);
882 w[pa.is_zero] = Word(0);
883 w[pa.is_sub] = Word(0);
884 w[pa.is_norm] = Word(0);
885
886 w[pb.sign] = Word(pb_sign << 63);
887 w[pb.exp] = Word(0);
888 w[pb.frac] = Word(0);
889 w[pb.is_nan] = Word(pb_is_nan);
890 w[pb.is_inf] = Word(pb_is_inf);
891 w[pb.is_zero] = Word(0);
892 w[pb.is_sub] = Word(0);
893 w[pb.is_norm] = Word(0);
894
895 w[finite_or_inf_wire] = Word(finite_or_inf);
896
897 let ref_result = ref_fp64_finish_specials(
898 pa_is_nan,
899 pa_is_inf,
900 pa_sign << 63,
901 pb_is_nan,
902 pb_is_inf,
903 pb_sign << 63,
904 finite_or_inf,
905 );
906 w[expected_result] = Word(ref_result);
907
908 circuit.populate_wire_witness(&mut w).unwrap();
909 let cs = circuit.constraint_system();
910 cs.verify(&w.into_value_vec()).unwrap();
911 }
912 }
913
914 #[test]
915 fn test_fp64_ext_sig_and_exp() {
916 let test_values = vec![1.0f64, -1.0f64, 2.0f64, f64::MIN_POSITIVE, 0.0f64];
917
918 for val in test_values {
919 let builder = CircuitBuilder::new();
920 let input = builder.add_inout();
921 let parts = fp64_unpack(&builder, input);
922 let (sig, exp_eff) = fp64_ext_sig_and_exp(&builder, &parts);
923 let expected_sig = builder.add_inout();
924 let expected_exp = builder.add_inout();
925
926 builder.assert_eq("sig", sig, expected_sig);
927 builder.assert_eq("exp_eff", exp_eff, expected_exp);
928
929 let circuit = builder.build();
930 let mut w = circuit.new_witness_filler();
931 w[input] = Word(val.to_bits());
932
933 let unpack_result = ref_fp64_unpack(val.to_bits());
935 let (exp_sig, exp_exp) = ref_fp64_ext_sig_and_exp(
936 unpack_result.sign,
937 unpack_result.exp,
938 unpack_result.frac,
939 unpack_result.is_norm,
940 );
941
942 w[expected_sig] = Word(exp_sig);
943 w[expected_exp] = Word(exp_exp);
944
945 circuit.populate_wire_witness(&mut w).unwrap();
946 let cs = circuit.constraint_system();
947 cs.verify(&w.into_value_vec()).unwrap();
948 }
949 }
950
951 #[test]
952 fn test_fp64_order_by_exp() {
953 let test_cases = [
954 (100, 5, 1, 200, 10, 0), (100, 10, 1, 200, 5, 0), (300, 15, 0, 400, 15, 1), ];
959
960 for (sig_a_val, exp_a_val, sign_a_val, sig_b_val, exp_b_val, sign_b_val) in test_cases {
961 let builder = CircuitBuilder::new();
962 let sig_a = builder.add_inout();
963 let exp_a = builder.add_inout();
964 let sign_a = builder.add_inout();
965 let sig_b = builder.add_inout();
966 let exp_b = builder.add_inout();
967 let sign_b = builder.add_inout();
968
969 let (sig_a_out, exp_a_out, sign_a_out, sig_b_out, exp_b_out, sign_b_out, swapped) =
970 fp64_order_by_exp(&builder, sig_a, exp_a, sign_a, sig_b, exp_b, sign_b);
971
972 let expected_sig_a = builder.add_inout();
973 let expected_exp_a = builder.add_inout();
974 let expected_sign_a = builder.add_inout();
975 let expected_sig_b = builder.add_inout();
976 let expected_exp_b = builder.add_inout();
977 let expected_sign_b = builder.add_inout();
978 let expected_swapped = builder.add_inout();
979 let bool_mask = builder.add_constant(Word::MSB_ONE);
980
981 builder.assert_eq("sig_a", sig_a_out, expected_sig_a);
982 builder.assert_eq("exp_a", exp_a_out, expected_exp_a);
983 builder.assert_eq("sign_a", sign_a_out, expected_sign_a);
984 builder.assert_eq("sig_b", sig_b_out, expected_sig_b);
985 builder.assert_eq("exp_b", exp_b_out, expected_exp_b);
986 builder.assert_eq("sign_b", sign_b_out, expected_sign_b);
987 builder.assert_eq("swapped", builder.band(swapped, bool_mask), expected_swapped);
988
989 let circuit = builder.build();
990 let mut w = circuit.new_witness_filler();
991
992 w[sig_a] = Word(sig_a_val);
993 w[exp_a] = Word(exp_a_val);
994 w[sign_a] = Word(sign_a_val);
995 w[sig_b] = Word(sig_b_val);
996 w[exp_b] = Word(exp_b_val);
997 w[sign_b] = Word(sign_b_val);
998
999 let (exp_sig_a, exp_exp_a, exp_sign_a, exp_sig_b, exp_exp_b, exp_sign_b, exp_swapped) =
1001 ref_fp64_order_by_exp(
1002 sig_a_val, exp_a_val, sign_a_val, sig_b_val, exp_b_val, sign_b_val,
1003 );
1004
1005 w[expected_sig_a] = Word(exp_sig_a);
1006 w[expected_exp_a] = Word(exp_exp_a);
1007 w[expected_sign_a] = Word(exp_sign_a);
1008 w[expected_sig_b] = Word(exp_sig_b);
1009 w[expected_exp_b] = Word(exp_exp_b);
1010 w[expected_sign_b] = Word(exp_sign_b);
1011 w[expected_swapped] = Word(exp_swapped);
1012
1013 circuit.populate_wire_witness(&mut w).unwrap();
1014 let cs = circuit.constraint_system();
1015 cs.verify(&w.into_value_vec()).unwrap();
1016 }
1017 }
1018
1019 #[test]
1020 fn test_float64_add_basic() {
1021 let tiny_sub = f64::from_bits(1); let half_ulp_at_1 = 2f64.powi(-53);
1023 let one_ulp_at_1 = 2f64.powi(-52);
1024 let big = 1e300f64;
1025 let small = 1e-300f64;
1026 let min_norm = f64::MIN_POSITIVE; let half_min_norm = min_norm / 2.0; let test_cases: &[(f64, f64)] = &[
1030 (0.0, 0.0),
1031 (0.0, -0.0),
1032 (-0.0, -0.0),
1033 (1.0, -1.0),
1034 (-1.0, 1.0),
1035 (1.0, 2.0),
1036 (1.5, 1.5),
1037 (2.0, -0.5),
1038 (1.0, half_ulp_at_1), (1.0, one_ulp_at_1), (tiny_sub, tiny_sub), (half_min_norm, half_min_norm), (min_norm, tiny_sub), (1.0, 2f64.powi(-1000)),
1047 (2f64.powi(100), 2f64.powi(-1000)),
1048 (big, big), (-big, -big), (small, small),
1052 (f64::INFINITY, 1.0),
1054 (-f64::INFINITY, -1.0),
1055 (f64::INFINITY, -f64::INFINITY), (f64::NAN, 1.0),
1058 (1.0, f64::NAN),
1059 (f64::NAN, f64::NAN),
1060 ];
1061
1062 for (i, (a_val, b_val)) in test_cases.iter().copied().enumerate() {
1063 let ref_result = ref_float64_add(a_val.to_bits(), b_val.to_bits());
1064 let native = (a_val + b_val).to_bits();
1065 assert!(
1066 f64_bits_semantic_eq(ref_result, native),
1067 "Test case {}: pseudo {} != native {}",
1068 i,
1069 ref_result,
1070 native
1071 );
1072
1073 let builder = CircuitBuilder::new();
1074 let a = builder.add_inout();
1075 let b = builder.add_inout();
1076 let result = float64_add(&builder, a, b);
1077 let expected = builder.add_inout();
1078 builder.assert_eq(format!("float64_add_case_{}", i), result, expected);
1079
1080 let circuit = builder.build();
1081 let mut w = circuit.new_witness_filler();
1082 w[a] = Word(a_val.to_bits());
1083 w[b] = Word(b_val.to_bits());
1084
1085 let expected_val = ref_result;
1086 w[expected] = Word(expected_val);
1087
1088 circuit.populate_wire_witness(&mut w).unwrap();
1089 let cs = circuit.constraint_system();
1090 cs.verify(&w.into_value_vec()).unwrap();
1091 }
1092 }
1093}