1pub(crate) use crate::algos::ln::ln_series_2limb::STRICT_GUARD;
81
82impl<const SCALE: u32> crate::D<crate::int::types::Int<2>, SCALE> {
83 #[inline]
110 #[must_use]
111 pub fn ln_strict(self) -> Self {
112 self.ln_strict_with(crate::support::rounding::DEFAULT_ROUNDING_MODE)
113 }
114
115 #[inline]
117 #[must_use]
118 pub fn ln_strict_with(self, mode: crate::support::rounding::RoundingMode) -> Self {
119 Self::from_bits(crate::policy::ln::dispatch::<_, SCALE>(self.to_bits(), mode))
120 }
121
122 #[inline]
126 #[must_use]
127 pub fn ln_approx(self, working_digits: u32) -> Self {
128 self.ln_approx_with(
129 working_digits,
130 crate::support::rounding::DEFAULT_ROUNDING_MODE,
131 )
132 }
133
134 #[inline]
136 #[must_use]
137 pub fn ln_approx_with(
138 self,
139 working_digits: u32,
140 mode: crate::support::rounding::RoundingMode,
141 ) -> Self {
142 if working_digits == STRICT_GUARD {
143 return self.ln_strict_with(mode);
144 }
145 Self::from_bits(crate::policy::ln::dispatch_with::<_, SCALE>(self.to_bits(), working_digits, mode))
146 }
147
148 #[cfg(all(feature = "strict", not(feature = "fast")))]
150 #[inline]
151 #[must_use]
152 pub fn ln(self) -> Self {
153 self.ln_strict()
154 }
155
156 #[inline]
185 #[must_use]
186 pub fn log1p_strict(self) -> Self {
187 self.log1p_strict_with(crate::support::rounding::DEFAULT_ROUNDING_MODE)
188 }
189
190 #[inline]
193 #[must_use]
194 pub fn log1p_strict_with(self, mode: crate::support::rounding::RoundingMode) -> Self {
195 Self::from_bits(crate::policy::log1p::dispatch::<_, SCALE>(self.to_bits(), mode))
196 }
197
198 #[inline]
202 #[must_use]
203 pub fn log1p_approx(self, working_digits: u32) -> Self {
204 self.log1p_approx_with(
205 working_digits,
206 crate::support::rounding::DEFAULT_ROUNDING_MODE,
207 )
208 }
209
210 #[inline]
212 #[must_use]
213 pub fn log1p_approx_with(
214 self,
215 working_digits: u32,
216 mode: crate::support::rounding::RoundingMode,
217 ) -> Self {
218 if working_digits == STRICT_GUARD {
219 return self.log1p_strict_with(mode);
220 }
221 Self::from_bits(crate::policy::log1p::dispatch_with::<_, SCALE>(
222 self.to_bits(),
223 working_digits,
224 mode,
225 ))
226 }
227
228 #[cfg(all(feature = "strict", not(feature = "fast")))]
230 #[inline]
231 #[must_use]
232 pub fn log1p(self) -> Self {
233 self.log1p_strict()
234 }
235
236 #[inline]
238 #[must_use]
239 pub fn log_strict(self, base: Self) -> Self {
240 self.log_strict_with(base, crate::support::rounding::DEFAULT_ROUNDING_MODE)
241 }
242
243 #[inline]
245 #[must_use]
246 pub fn log_strict_with(self, base: Self, mode: crate::support::rounding::RoundingMode) -> Self {
247 Self::from_bits(crate::policy::log::dispatch::<_, SCALE>(self.to_bits(), base.to_bits(), mode))
248 }
249
250 #[inline]
252 #[must_use]
253 pub fn log_approx(self, base: Self, working_digits: u32) -> Self {
254 self.log_approx_with(
255 base,
256 working_digits,
257 crate::support::rounding::DEFAULT_ROUNDING_MODE,
258 )
259 }
260
261 #[inline]
263 #[must_use]
264 pub fn log_approx_with(
265 self,
266 base: Self,
267 working_digits: u32,
268 mode: crate::support::rounding::RoundingMode,
269 ) -> Self {
270 if working_digits == STRICT_GUARD {
271 return self.log_strict_with(base, mode);
272 }
273 Self::from_bits(crate::policy::log::dispatch_with::<_, SCALE>(self.to_bits(), base.to_bits(), working_digits, mode))
274 }
275
276 #[cfg(all(feature = "strict", not(feature = "fast")))]
278 #[inline]
279 #[must_use]
280 pub fn log(self, base: Self) -> Self {
281 self.log_strict(base)
282 }
283
284 #[inline]
286 #[must_use]
287 pub fn log2_strict(self) -> Self {
288 self.log2_strict_with(crate::support::rounding::DEFAULT_ROUNDING_MODE)
289 }
290
291 #[inline]
293 #[must_use]
294 pub fn log2_strict_with(self, mode: crate::support::rounding::RoundingMode) -> Self {
295 Self::from_bits(crate::policy::ln::log2_dispatch::<_, SCALE>(self.to_bits(), mode))
296 }
297
298 #[inline]
300 #[must_use]
301 pub fn log2_approx(self, working_digits: u32) -> Self {
302 self.log2_approx_with(
303 working_digits,
304 crate::support::rounding::DEFAULT_ROUNDING_MODE,
305 )
306 }
307
308 #[inline]
310 #[must_use]
311 pub fn log2_approx_with(
312 self,
313 working_digits: u32,
314 mode: crate::support::rounding::RoundingMode,
315 ) -> Self {
316 if working_digits == STRICT_GUARD {
317 return self.log2_strict_with(mode);
318 }
319 Self::from_bits(crate::policy::ln::log2_dispatch_with::<_, SCALE>(self.to_bits(), working_digits, mode))
320 }
321
322 #[cfg(all(feature = "strict", not(feature = "fast")))]
324 #[inline]
325 #[must_use]
326 pub fn log2(self) -> Self {
327 self.log2_strict()
328 }
329
330 #[inline]
332 #[must_use]
333 pub fn log10_strict(self) -> Self {
334 self.log10_strict_with(crate::support::rounding::DEFAULT_ROUNDING_MODE)
335 }
336
337 #[inline]
339 #[must_use]
340 pub fn log10_strict_with(self, mode: crate::support::rounding::RoundingMode) -> Self {
341 Self::from_bits(crate::policy::ln::log10_dispatch::<_, SCALE>(self.to_bits(), mode))
342 }
343
344 #[inline]
346 #[must_use]
347 pub fn log10_approx(self, working_digits: u32) -> Self {
348 self.log10_approx_with(
349 working_digits,
350 crate::support::rounding::DEFAULT_ROUNDING_MODE,
351 )
352 }
353
354 #[inline]
356 #[must_use]
357 pub fn log10_approx_with(
358 self,
359 working_digits: u32,
360 mode: crate::support::rounding::RoundingMode,
361 ) -> Self {
362 if working_digits == STRICT_GUARD {
363 return self.log10_strict_with(mode);
364 }
365 Self::from_bits(crate::policy::ln::log10_dispatch_with::<_, SCALE>(self.to_bits(), working_digits, mode))
366 }
367
368 #[cfg(all(feature = "strict", not(feature = "fast")))]
370 #[inline]
371 #[must_use]
372 pub fn log10(self) -> Self {
373 self.log10_strict()
374 }
375
376 #[inline]
380 #[must_use]
381 pub fn exp_strict(self) -> Self {
382 self.exp_strict_with(crate::support::rounding::DEFAULT_ROUNDING_MODE)
383 }
384
385 #[inline]
387 #[must_use]
388 pub fn exp_strict_with(self, mode: crate::support::rounding::RoundingMode) -> Self {
389 Self::from_bits(crate::policy::exp::dispatch::<_, SCALE>(self.to_bits(), mode))
390 }
391
392 #[inline]
394 #[must_use]
395 pub fn exp_approx(self, working_digits: u32) -> Self {
396 self.exp_approx_with(
397 working_digits,
398 crate::support::rounding::DEFAULT_ROUNDING_MODE,
399 )
400 }
401
402 #[inline]
404 #[must_use]
405 pub fn exp_approx_with(
406 self,
407 working_digits: u32,
408 mode: crate::support::rounding::RoundingMode,
409 ) -> Self {
410 if working_digits == STRICT_GUARD {
411 return self.exp_strict_with(mode);
412 }
413 Self::from_bits(crate::policy::exp::dispatch_with::<_, SCALE>(self.to_bits(), working_digits, mode))
414 }
415
416 #[cfg(all(feature = "strict", not(feature = "fast")))]
418 #[inline]
419 #[must_use]
420 pub fn exp(self) -> Self {
421 self.exp_strict()
422 }
423
424 #[inline]
468 #[must_use]
469 pub fn expm1_strict(self) -> Self {
470 self.expm1_strict_with(crate::support::rounding::DEFAULT_ROUNDING_MODE)
471 }
472
473 #[inline]
476 #[must_use]
477 pub fn expm1_strict_with(self, mode: crate::support::rounding::RoundingMode) -> Self {
478 Self::from_bits(crate::policy::expm1::dispatch::<_, SCALE>(self.to_bits(), mode))
479 }
480
481 #[inline]
485 #[must_use]
486 pub fn expm1_approx(self, working_digits: u32) -> Self {
487 self.expm1_approx_with(
488 working_digits,
489 crate::support::rounding::DEFAULT_ROUNDING_MODE,
490 )
491 }
492
493 #[inline]
495 #[must_use]
496 pub fn expm1_approx_with(
497 self,
498 working_digits: u32,
499 mode: crate::support::rounding::RoundingMode,
500 ) -> Self {
501 if working_digits == STRICT_GUARD {
502 return self.expm1_strict_with(mode);
503 }
504 Self::from_bits(crate::policy::expm1::dispatch_with::<_, SCALE>(
505 self.to_bits(),
506 working_digits,
507 mode,
508 ))
509 }
510
511 #[cfg(all(feature = "strict", not(feature = "fast")))]
513 #[inline]
514 #[must_use]
515 pub fn expm1(self) -> Self {
516 self.expm1_strict()
517 }
518
519 #[inline]
521 #[must_use]
522 pub fn exp2_strict(self) -> Self {
523 self.exp2_strict_with(crate::support::rounding::DEFAULT_ROUNDING_MODE)
524 }
525
526 #[inline]
528 #[must_use]
529 pub fn exp2_strict_with(self, mode: crate::support::rounding::RoundingMode) -> Self {
530 Self::from_bits(crate::policy::exp::exp2_dispatch::<_, SCALE>(self.to_bits(), mode))
531 }
532
533 #[inline]
535 #[must_use]
536 pub fn exp2_approx(self, working_digits: u32) -> Self {
537 self.exp2_approx_with(
538 working_digits,
539 crate::support::rounding::DEFAULT_ROUNDING_MODE,
540 )
541 }
542
543 #[inline]
545 #[must_use]
546 pub fn exp2_approx_with(
547 self,
548 working_digits: u32,
549 mode: crate::support::rounding::RoundingMode,
550 ) -> Self {
551 if working_digits == STRICT_GUARD {
552 return self.exp2_strict_with(mode);
553 }
554 Self::from_bits(crate::policy::exp::exp2_dispatch_with::<_, SCALE>(self.to_bits(), working_digits, mode))
555 }
556
557 #[cfg(all(feature = "strict", not(feature = "fast")))]
559 #[inline]
560 #[must_use]
561 pub fn exp2(self) -> Self {
562 self.exp2_strict()
563 }
564}
565
566#[cfg(all(test, feature = "strict", not(feature = "fast")))]
567mod strict_tests {
568 use crate::types::widths::D38s12;
569
570 const STRICT_TOLERANCE_LSB: i128 = 2;
574
575 fn within(actual: D38s12, expected_bits: i128, tolerance: i128) -> bool {
576 (actual.to_bits().as_i128() - expected_bits).abs() <= tolerance
577 }
578
579 #[test]
581 fn ln_of_one_is_zero() {
582 assert_eq!(D38s12::ONE.ln(), D38s12::ZERO);
583 }
584
585 #[test]
590 fn ln_strict_is_correctly_rounded_vs_f64() {
591 fn check(raw: i128) {
592 let value = crate::D::<crate::int::types::Int<2>, 9>::from_bits(crate::int::types::Int::<2>::from_i128(raw));
593 let strict = value.ln_strict().to_bits().as_i128();
594 let reference = {
595 let as_float = raw as f64 / 1e9;
596 (as_float.ln() * 1e9).round() as i128
597 };
598 assert!(
599 (strict - reference).abs() <= 1,
600 "ln_strict({raw}) = {strict}, f64 reference {reference}"
601 );
602 }
603 for &raw in &[
604 1,
605 500_000_000,
606 1_000_000_000,
607 1_500_000_000,
608 2_000_000_000,
609 2_718_281_828,
610 10_000_000_000,
611 123_456_789_012_345,
612 999_999_999_999_999_999,
613 i64::MAX as i128,
614 ] {
615 check(raw);
616 }
617 }
618
619 #[test]
622 fn strict_log_exp_family_matches_f64() {
623 fn check_exp(raw: i128) {
624 let value = crate::D::<crate::int::types::Int<2>, 9>::from_bits(crate::int::types::Int::<2>::from_i128(raw));
625 let strict = value.exp_strict().to_bits().as_i128();
626 let reference = ((raw as f64 / 1e9).exp() * 1e9).round() as i128;
627 assert!(
628 (strict - reference).abs() <= 1,
629 "exp_strict({raw}) = {strict}, f64 reference {reference}"
630 );
631 }
632 fn check_log2(raw: i128) {
633 let value = crate::D::<crate::int::types::Int<2>, 9>::from_bits(crate::int::types::Int::<2>::from_i128(raw));
634 let strict = value.log2_strict().to_bits().as_i128();
635 let reference = ((raw as f64 / 1e9).log2() * 1e9).round() as i128;
636 assert!(
637 (strict - reference).abs() <= 1,
638 "log2_strict({raw}) = {strict}, f64 reference {reference}"
639 );
640 }
641 fn check_log10(raw: i128) {
642 let value = crate::D::<crate::int::types::Int<2>, 9>::from_bits(crate::int::types::Int::<2>::from_i128(raw));
643 let strict = value.log10_strict().to_bits().as_i128();
644 let reference = ((raw as f64 / 1e9).log10() * 1e9).round() as i128;
645 assert!(
646 (strict - reference).abs() <= 1,
647 "log10_strict({raw}) = {strict}, f64 reference {reference}"
648 );
649 }
650 for &raw in &[
651 -5_000_000_000,
652 -1_000_000_000,
653 -500_000_000,
654 1,
655 500_000_000,
656 1_000_000_000,
657 2_000_000_000,
658 5_000_000_000,
659 10_000_000_000,
660 ] {
661 check_exp(raw);
662 }
663 for &raw in &[
664 1,
665 500_000_000,
666 1_000_000_000,
667 2_000_000_000,
668 8_000_000_000,
669 10_000_000_000,
670 123_456_789_012_345,
671 i64::MAX as i128,
672 ] {
673 check_log2(raw);
674 check_log10(raw);
675 }
676 }
677
678 #[test]
680 fn strict_exp2_at_integers() {
681 for k in 0_i128..=12 {
682 let value = crate::D::<crate::int::types::Int<2>, 12>::from_bits(crate::int::types::Int::<2>::from_i128(k * 10i128.pow(12)));
683 let got = value.exp2_strict().to_bits().as_i128();
684 let expected = (1i128 << k) * 10i128.pow(12);
685 assert_eq!(got, expected, "2^{k}");
686 }
687 }
688
689 #[test]
691 fn ln_strict_of_powers_of_two() {
692 let ln2_s18: i128 = 693_147_180_559_945_309;
693 for k in 1_i128..=20 {
694 let value = crate::D::<crate::int::types::Int<2>, 18>::from_bits(crate::int::types::Int::<2>::from_i128((1i128 << k) * 10i128.pow(18)));
695 let got = value.ln_strict().to_bits().as_i128();
696 let expected = k * ln2_s18;
697 let tolerance = k / 2 + 2;
698 assert!(
699 (got - expected).abs() <= tolerance,
700 "ln(2^{k}) = {got}, expected ≈ {expected}"
701 );
702 }
703 }
704
705 #[test]
707 fn ln_of_two_close_to_canonical() {
708 let two = D38s12::from_bits(crate::int::types::Int::<2>::from_i128(2_000_000_000_000));
709 let result = two.ln();
710 assert!(
711 within(result, 693_147_180_560, STRICT_TOLERANCE_LSB),
712 "ln(2) bits = {}",
713 result.to_bits().as_i128()
714 );
715 }
716
717 #[test]
719 fn ln_of_e_close_to_one() {
720 let e_at_s12 = D38s12::from_bits(crate::int::types::Int::<2>::from_i128(2_718_281_828_459));
721 let result = e_at_s12.ln();
722 assert!(
723 within(result, 1_000_000_000_000, STRICT_TOLERANCE_LSB),
724 "ln(e) bits = {}, expected ~1_000_000_000_000",
725 result.to_bits().as_i128()
726 );
727 }
728
729 #[test]
731 fn ln_of_ten_close_to_canonical() {
732 let ten = D38s12::from_bits(crate::int::types::Int::<2>::from_i128(10_000_000_000_000));
733 let result = ten.ln();
734 assert!(
735 within(result, 2_302_585_092_994, STRICT_TOLERANCE_LSB),
736 "ln(10) bits = {}, expected ~2_302_585_092_994",
737 result.to_bits().as_i128()
738 );
739 }
740
741 #[test]
743 fn ln_above_one_is_positive() {
744 let value = D38s12::from_bits(crate::int::types::Int::<2>::from_i128(1_500_000_000_000));
745 let result = value.ln();
746 assert!(result.to_bits().as_i128() > 0);
747 }
748
749 #[test]
751 fn ln_below_one_is_negative() {
752 let value = D38s12::from_bits(crate::int::types::Int::<2>::from_i128(500_000_000_000));
753 let result = value.ln();
754 assert!(result.to_bits().as_i128() < 0);
755 assert!(
756 within(result, -693_147_180_560, STRICT_TOLERANCE_LSB),
757 "ln(0.5) bits = {}, expected ~-693_147_180_560",
758 result.to_bits().as_i128()
759 );
760 }
761
762 #[test]
763 #[should_panic(expected = "argument must be positive")]
764 fn ln_of_zero_panics() {
765 let _ = D38s12::ZERO.ln();
766 }
767
768 #[test]
769 #[should_panic(expected = "argument must be positive")]
770 fn ln_of_negative_panics() {
771 let negative_value = D38s12::from_bits(crate::int::types::Int::<2>::from_i128(-1_000_000_000_000));
772 let _ = negative_value.ln();
773 }
774
775 const DERIVED_LOG_TOLERANCE_LSB: i128 = 20;
778
779 #[test]
781 fn log2_of_two_is_one() {
782 let two = D38s12::from_bits(crate::int::types::Int::<2>::from_i128(2_000_000_000_000));
783 let result = two.log2();
784 assert!(
785 within(result, 1_000_000_000_000, DERIVED_LOG_TOLERANCE_LSB),
786 "log2(2) bits = {}",
787 result.to_bits().as_i128()
788 );
789 }
790
791 #[test]
793 fn log2_of_eight_is_three() {
794 let eight = D38s12::from_bits(crate::int::types::Int::<2>::from_i128(8_000_000_000_000));
795 let result = eight.log2();
796 assert!(
797 within(result, 3_000_000_000_000, DERIVED_LOG_TOLERANCE_LSB),
798 "log2(8) bits = {}",
799 result.to_bits().as_i128()
800 );
801 }
802
803 #[test]
805 fn log10_of_ten_is_one() {
806 let ten = D38s12::from_bits(crate::int::types::Int::<2>::from_i128(10_000_000_000_000));
807 let result = ten.log10();
808 assert!(
809 within(result, 1_000_000_000_000, DERIVED_LOG_TOLERANCE_LSB),
810 "log10(10) bits = {}",
811 result.to_bits().as_i128()
812 );
813 }
814
815 #[test]
817 fn log10_of_hundred_is_two() {
818 let hundred = D38s12::from_bits(crate::int::types::Int::<2>::from_i128(100_000_000_000_000));
819 let result = hundred.log10();
820 assert!(
821 within(result, 2_000_000_000_000, DERIVED_LOG_TOLERANCE_LSB),
822 "log10(100) bits = {}",
823 result.to_bits().as_i128()
824 );
825 }
826
827 #[test]
829 fn log_self_is_one() {
830 let base = D38s12::from_bits(crate::int::types::Int::<2>::from_i128(5_000_000_000_000));
831 let result = base.log(base);
832 assert!(
833 within(result, 1_000_000_000_000, DERIVED_LOG_TOLERANCE_LSB),
834 "log_5(5) bits = {}",
835 result.to_bits().as_i128()
836 );
837 }
838
839 #[test]
841 fn log_with_base_two() {
842 let eight = D38s12::from_bits(crate::int::types::Int::<2>::from_i128(8_000_000_000_000));
843 let two = D38s12::from_bits(crate::int::types::Int::<2>::from_i128(2_000_000_000_000));
844 let result = eight.log(two);
845 assert!(
846 within(result, 3_000_000_000_000, DERIVED_LOG_TOLERANCE_LSB),
847 "log_2(8) bits = {}",
848 result.to_bits().as_i128()
849 );
850 }
851
852 #[test]
853 #[should_panic(expected = "base must not equal 1")]
854 fn log_base_one_panics() {
855 let value = D38s12::from_bits(crate::int::types::Int::<2>::from_i128(5_000_000_000_000));
856 let one = D38s12::ONE;
857 let _ = value.log(one);
858 }
859
860 const EXP_TOLERANCE_LSB: i128 = 20;
863
864 #[test]
866 fn exp_of_zero_is_one() {
867 assert_eq!(D38s12::ZERO.exp(), D38s12::ONE);
868 }
869
870 #[test]
872 fn exp_of_one_is_e() {
873 let result = D38s12::ONE.exp();
874 assert!(
875 within(result, 2_718_281_828_459, EXP_TOLERANCE_LSB),
876 "exp(1) bits = {}",
877 result.to_bits().as_i128()
878 );
879 }
880
881 #[test]
883 fn exp_of_ln_2_is_two() {
884 let ln_2 = D38s12::from_bits(crate::int::types::Int::<2>::from_i128(693_147_180_560));
885 let result = ln_2.exp();
886 assert!(
887 within(result, 2_000_000_000_000, EXP_TOLERANCE_LSB),
888 "exp(ln 2) bits = {}",
889 result.to_bits().as_i128()
890 );
891 }
892
893 #[test]
895 fn exp_of_negative_one_is_reciprocal_e() {
896 let neg_one = D38s12::from_bits(crate::int::types::Int::<2>::from_i128(-1_000_000_000_000));
897 let result = neg_one.exp();
898 assert!(
899 within(result, 367_879_441_171, EXP_TOLERANCE_LSB),
900 "exp(-1) bits = {}",
901 result.to_bits().as_i128()
902 );
903 }
904
905 #[test]
907 fn exp2_of_zero_is_one() {
908 assert_eq!(D38s12::ZERO.exp2(), D38s12::ONE);
909 }
910
911 #[test]
913 fn exp2_of_one_is_two() {
914 let result = D38s12::ONE.exp2();
915 assert!(
916 within(result, 2_000_000_000_000, EXP_TOLERANCE_LSB),
917 "exp2(1) bits = {}",
918 result.to_bits().as_i128()
919 );
920 }
921
922 #[test]
924 fn exp2_of_ten_is_1024() {
925 let ten = D38s12::from_bits(crate::int::types::Int::<2>::from_i128(10_000_000_000_000));
926 let result = ten.exp2();
927 assert!(
928 within(result, 1_024_000_000_000_000, EXP_TOLERANCE_LSB * 10),
929 "exp2(10) bits = {}",
930 result.to_bits().as_i128()
931 );
932 }
933}