use std::ops::{Add, Div, Mul, Sub};
use crate::{
error::Fallible,
traits::{ExactIntCast, InfCast},
};
use dashu::{
base::{EstimatedLog2, SquareRoot},
float::{
FBig,
round::mode::{Down, Up},
},
integer::IBig,
};
use std::panic;
fn catch_unwind_silent<R>(f: impl FnOnce() -> R + panic::UnwindSafe) -> std::thread::Result<R> {
let prev_hook = panic::take_hook();
panic::set_hook(Box::new(|_| {}));
let result = panic::catch_unwind(f);
panic::set_hook(prev_hook);
result
}
pub trait AlertingAbs: Sized {
fn alerting_abs(&self) -> Fallible<Self>;
}
pub trait AlertingAdd: Sized {
fn alerting_add(&self, v: &Self) -> Fallible<Self>;
}
pub trait AlertingSub: Sized {
fn alerting_sub(&self, v: &Self) -> Fallible<Self>;
}
pub trait AlertingMul: Sized {
fn alerting_mul(&self, v: &Self) -> Fallible<Self>;
}
pub trait AlertingDiv: Sized {
fn alerting_div(&self, v: &Self) -> Fallible<Self>;
}
pub trait AlertingPow: Sized {
fn alerting_pow(&self, p: &Self) -> Fallible<Self>;
}
pub trait SaturatingAdd: Sized {
fn saturating_add(&self, v: &Self) -> Self;
}
pub trait SaturatingMul: Sized {
fn saturating_mul(&self, v: &Self) -> Self;
}
pub trait InfExp: Sized {
fn inf_exp(self) -> Fallible<Self>;
fn neg_inf_exp(self) -> Fallible<Self>;
}
pub trait InfLn: Sized {
fn inf_ln(self) -> Fallible<Self>;
fn neg_inf_ln(self) -> Fallible<Self>;
}
pub trait InfLog2: Sized {
fn inf_log2(self) -> Fallible<Self>;
fn neg_inf_log2(self) -> Fallible<Self>;
}
pub trait InfSqrt: Sized {
fn inf_sqrt(self) -> Fallible<Self>;
fn neg_inf_sqrt(self) -> Fallible<Self>;
}
pub trait InfPowI: Sized + AlertingPow {
fn inf_powi(&self, p: IBig) -> Fallible<Self>;
fn neg_inf_powi(&self, p: IBig) -> Fallible<Self>;
}
pub trait InfAdd: Sized + AlertingAdd {
fn inf_add(&self, v: &Self) -> Fallible<Self>;
fn neg_inf_add(&self, v: &Self) -> Fallible<Self>;
}
pub trait InfSub: Sized + AlertingSub {
fn inf_sub(&self, v: &Self) -> Fallible<Self>;
fn neg_inf_sub(&self, v: &Self) -> Fallible<Self>;
}
pub trait InfMul: Sized + AlertingMul {
fn inf_mul(&self, v: &Self) -> Fallible<Self>;
fn neg_inf_mul(&self, v: &Self) -> Fallible<Self>;
}
pub trait InfDiv: Sized + AlertingDiv {
fn inf_div(&self, v: &Self) -> Fallible<Self>;
fn neg_inf_div(&self, v: &Self) -> Fallible<Self>;
}
pub trait InfExpM1: Sized {
fn inf_exp_m1(self) -> Fallible<Self>;
fn neg_inf_exp_m1(self) -> Fallible<Self>;
}
pub trait InfLn1P: Sized {
fn inf_ln_1p(self) -> Fallible<Self>;
fn neg_inf_ln_1p(self) -> Fallible<Self>;
}
macro_rules! impl_alerting_abs_signed_int {
($($ty:ty),+) => ($(impl AlertingAbs for $ty {
fn alerting_abs(&self) -> Fallible<Self> {
self.checked_abs().ok_or_else(|| err!(Overflow,
"the corresponding positive value for {} is out of range", self))
}
})+)
}
impl_alerting_abs_signed_int!(i8, i16, i32, i64, i128, isize);
macro_rules! impl_alerting_abs_unsigned_int {
($($ty:ty),+) => ($(impl AlertingAbs for $ty {
fn alerting_abs(&self) -> Fallible<Self> {
Ok(*self)
}
})+)
}
impl_alerting_abs_unsigned_int!(u8, u16, u32, u64, u128, usize);
macro_rules! impl_alerting_abs_float {
($($ty:ty),+) => ($(impl AlertingAbs for $ty {
fn alerting_abs(&self) -> Fallible<Self> {
Ok(self.abs())
}
})+)
}
impl_alerting_abs_float!(f32, f64);
macro_rules! impl_alerting_int {
($($t:ty),+) => {
$(impl SaturatingAdd for $t {
#[inline]
fn saturating_add(&self, v: &Self) -> Self {
<$t>::saturating_add(*self, *v)
}
})
+$(impl SaturatingMul for $t {
#[inline]
fn saturating_mul(&self, v: &Self) -> Self {
<$t>::saturating_mul(*self, *v)
}
})+
$(impl AlertingMul for $t {
#[inline]
fn alerting_mul(&self, v: &Self) -> Fallible<Self> {
<$t>::checked_mul(*self, *v).ok_or_else(|| err!(
Overflow,
"{} * {} overflows. Consider tightening your parameters.",
self, v))
}
})+
$(impl AlertingDiv for $t {
#[inline]
fn alerting_div(&self, v: &Self) -> Fallible<Self> {
<$t>::checked_div(*self, *v).ok_or_else(|| err!(
Overflow,
"{} / {} overflows. Consider tightening your parameters.",
self, v))
}
})+
$(impl AlertingAdd for $t {
#[inline]
fn alerting_add(&self, v: &Self) -> Fallible<Self> {
<$t>::checked_add(*self, *v).ok_or_else(|| err!(
Overflow,
"{} + {} overflows. Consider tightening your parameters.",
self, v))
}
})+
$(impl AlertingSub for $t {
#[inline]
fn alerting_sub(&self, v: &Self) -> Fallible<Self> {
<$t>::checked_sub(*self, *v).ok_or_else(|| err!(
Overflow,
"{} - {} overflows. Consider tightening your parameters.",
self, v))
}
})+
$(impl AlertingPow for $t {
#[inline]
fn alerting_pow(&self, p: &Self) -> Fallible<Self> {
let p = u32::exact_int_cast(*p)?;
<$t>::checked_pow(*self, p).ok_or_else(|| err!(
Overflow,
"{}.pow({}) overflows. Consider tightening your parameters.",
self, p))
}
})+
};
}
impl_alerting_int!(
u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize
);
macro_rules! impl_alerting_float {
($($t:ty),+) => {
$(impl SaturatingAdd for $t {
fn saturating_add(&self, v: &Self) -> Self {
(self + v).clamp(<$t>::MIN, <$t>::MAX)
}
})+
$(impl SaturatingMul for $t {
fn saturating_mul(&self, v: &Self) -> Self {
(self * v).clamp(<$t>::MIN, <$t>::MAX)
}
})+
$(impl AlertingMul for $t {
fn alerting_mul(&self, v: &Self) -> Fallible<Self> {
let y = self * v;
y.is_finite().then(|| y).ok_or_else(|| err!(
Overflow,
"{} * {} is not finite. Consider tightening your parameters.",
self, v))
}
})+
$(impl AlertingDiv for $t {
fn alerting_div(&self, v: &Self) -> Fallible<Self> {
let y = self / v;
y.is_finite().then(|| y).ok_or_else(|| err!(
Overflow,
"{} / {} is not finite. Consider tightening your parameters.",
self, v))
}
})+
$(impl AlertingAdd for $t {
fn alerting_add(&self, v: &Self) -> Fallible<Self> {
let y = self + v;
y.is_finite().then(|| y).ok_or_else(|| err!(
Overflow,
"{} + {} is not finite. Consider tightening your parameters.",
self, v))
}
})+
$(impl AlertingSub for $t {
fn alerting_sub(&self, v: &Self) -> Fallible<Self> {
let y = self - v;
y.is_finite().then(|| y).ok_or_else(|| err!(
Overflow,
"{} - {} is not finite. Consider tightening your parameters.",
self, v))
}
})+
$(impl AlertingPow for $t {
fn alerting_pow(&self, v: &Self) -> Fallible<Self> {
let y = self.powf(*v);
y.is_finite().then(|| y).ok_or_else(|| err!(
Overflow,
"{} - {} is not finite. Consider tightening your parameters.",
self, v))
}
})+
}
}
impl_alerting_float!(f32, f64);
trait Log2 {
fn log2(self) -> Self;
}
impl Log2 for FBig<Down> {
fn log2(self) -> Self {
Self::try_from(self.log2_bounds().0).unwrap()
}
}
impl Log2 for FBig<Up> {
fn log2(self) -> Self {
Self::try_from(self.log2_bounds().1).unwrap()
}
}
macro_rules! impl_float_inf_uni {
($($ty:ty),+; $name:ident, $method_inf:ident, $method_neg_inf:ident, $op:ident) => {
$(
impl $name for $ty {
fn $method_inf(self) -> Fallible<Self> {
let not_finite = || err!(
Overflow,
concat!("({}).", stringify!($method_inf), "() is not finite. Consider tightening your parameters."),
self);
if !self.$op().is_finite() {
return Err(not_finite());
}
let lhs = FBig::<Up>::inf_cast(self)?.with_precision(<$ty>::MANTISSA_DIGITS as usize).value();
let Ok(output) = catch_unwind_silent(|| lhs.$op()) else {
return Err(not_finite())
};
let output = Self::inf_cast(output)?;
output.is_finite().then(|| output).ok_or_else(not_finite)
}
fn $method_neg_inf(self) -> Fallible<Self> {
let not_finite = || err!(
Overflow,
concat!("({}).", stringify!($method_neg_inf), "() is not finite. Consider tightening your parameters."),
self);
if !self.$op().is_finite() {
return Err(not_finite());
}
let lhs = FBig::<Down>::inf_cast(self)?;
let Ok(output) = catch_unwind_silent(|| lhs.$op()) else {
return Err(not_finite())
};
let output = Self::neg_inf_cast(output)?;
output.is_finite().then(|| output).ok_or_else(not_finite)
}
})+
}
}
impl_float_inf_uni!(f64, f32; InfLn, inf_ln, neg_inf_ln, ln);
impl_float_inf_uni!(f64, f32; InfLog2, inf_log2, neg_inf_log2, log2);
impl_float_inf_uni!(f64, f32; InfLn1P, inf_ln_1p, neg_inf_ln_1p, ln_1p);
impl_float_inf_uni!(f64, f32; InfExpM1, inf_exp_m1, neg_inf_exp_m1, exp_m1);
impl_float_inf_uni!(f64, f32; InfSqrt, inf_sqrt, neg_inf_sqrt, sqrt);
impl InfExp for f64 {
fn inf_exp(self) -> Fallible<Self> {
let not_finite = || {
err!(
Overflow,
"({}).inf_exp() is not finite. Consider tightening your parameters.",
self
)
};
if !self.exp().is_finite() {
return Err(not_finite());
}
let lhs = FBig::<Up>::inf_cast(self)?
.with_precision(<f64>::MANTISSA_DIGITS as usize)
.value();
let Ok(output) = catch_unwind_silent(|| lhs.exp()) else {
if self.is_sign_negative() {
return Ok(f64::from_bits(1));
}
return Err(not_finite());
};
let output = Self::inf_cast(output)?;
output.is_finite().then(|| output).ok_or_else(not_finite)
}
fn neg_inf_exp(self) -> Fallible<Self> {
let not_finite = || {
err!(
Overflow,
"({}).neg_inf_exp() is not finite. Consider tightening your parameters.",
self
)
};
if !self.exp().is_finite() {
return Err(not_finite());
}
let lhs = FBig::<Down>::inf_cast(self)?;
let Ok(output) = catch_unwind_silent(|| lhs.exp()) else {
if self.is_sign_negative() {
return Ok(0.0);
}
return Err(not_finite());
};
let output = Self::neg_inf_cast(output)?;
output.is_finite().then(|| output).ok_or_else(not_finite)
}
}
impl InfExp for f32 {
fn inf_exp(self) -> Fallible<Self> {
let not_finite = || {
err!(
Overflow,
"({}).inf_exp() is not finite. Consider tightening your parameters.",
self
)
};
if !self.exp().is_finite() {
return Err(not_finite());
}
let lhs = FBig::<Up>::inf_cast(self)?
.with_precision(<f32>::MANTISSA_DIGITS as usize)
.value();
let Ok(output) = catch_unwind_silent(|| lhs.exp()) else {
if self.is_sign_negative() {
return Ok(f32::from_bits(1));
}
return Err(not_finite());
};
let output = Self::inf_cast(output)?;
output.is_finite().then(|| output).ok_or_else(not_finite)
}
fn neg_inf_exp(self) -> Fallible<Self> {
let not_finite = || {
err!(
Overflow,
"({}).neg_inf_exp() is not finite. Consider tightening your parameters.",
self
)
};
if !self.exp().is_finite() {
return Err(not_finite());
}
let lhs = FBig::<Down>::inf_cast(self)?;
let Ok(output) = catch_unwind_silent(|| lhs.exp()) else {
if self.is_sign_negative() {
return Ok(0.0);
}
return Err(not_finite());
};
let output = Self::neg_inf_cast(output)?;
output.is_finite().then(|| output).ok_or_else(not_finite)
}
}
macro_rules! impl_int_inf {
($ty:ty, $name:ident, $method_inf:ident, $method_neg_inf:ident, $func:ident) =>
(impl $name for $ty {
fn $method_inf(&self, other: &Self) -> Fallible<Self> {
self.$func(other)
}
fn $method_neg_inf(&self, other: &Self) -> Fallible<Self> {
self.$func(other)
}
});
($($ty:ty),+) => {
$(impl_int_inf!{$ty, InfAdd, inf_add, neg_inf_add, alerting_add})+
$(impl_int_inf!{$ty, InfSub, inf_sub, neg_inf_sub, alerting_sub})+
$(impl_int_inf!{$ty, InfMul, inf_mul, neg_inf_mul, alerting_mul})+
$(impl InfDiv for $ty {
fn inf_div(&self, other: &Self) -> Fallible<Self> {
if other == &0 {
return fallible!(Overflow, "attempt to divide by zero");
}
Ok(num::Integer::div_ceil(self, other))
}
fn neg_inf_div(&self, other: &Self) -> Fallible<Self> {
if other == &0 {
return fallible!(Overflow, "attempt to divide by zero");
}
Ok(num::Integer::div_floor(self, other))
}
})+
}
}
impl_int_inf!(
u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize
);
macro_rules! impl_float_inf_bi {
($($ty:ty),+; $name:ident, $method_inf:ident, $method_neg_inf:ident, $op:ident) => {
$(impl $name for $ty {
fn $method_inf(&self, other: &Self) -> Fallible<Self> {
let not_finite = || err!(
Overflow,
concat!("({}).", stringify!($method_inf), "({}) is not finite. Consider tightening your parameters."),
self, other);
if !self.$op(other).is_finite() {
return Err(not_finite());
}
let lhs = FBig::<Up>::try_from(*self)?;
let rhs = FBig::<Up>::try_from(*other)?;
let Ok(output) = catch_unwind_silent(|| lhs.$op(rhs)) else {
return Err(not_finite())
};
let output = Self::inf_cast(output)?;
output.is_finite().then(|| output).ok_or_else(not_finite)
}
fn $method_neg_inf(&self, other: &Self) -> Fallible<Self> {
let not_finite = || err!(
Overflow,
concat!("({}).", stringify!($method_neg_inf), "({}) is not finite. Consider tightening your parameters."),
self, other);
if !self.$op(other).is_finite() {
return Err(not_finite());
}
let lhs = FBig::<Down>::try_from(*self)?;
let rhs = FBig::<Down>::try_from(*other)?;
let Ok(output) = catch_unwind_silent(|| lhs.$op(rhs)) else {
return Err(not_finite())
};
let output = Self::neg_inf_cast(output)?;
output.is_finite().then(|| output).ok_or_else(not_finite)
}
})+
}
}
impl_float_inf_bi!(f64, f32; InfAdd, inf_add, neg_inf_add, add);
impl_float_inf_bi!(f64, f32; InfSub, inf_sub, neg_inf_sub, sub);
impl_float_inf_bi!(f64, f32; InfMul, inf_mul, neg_inf_mul, mul);
impl_float_inf_bi!(f64, f32; InfDiv, inf_div, neg_inf_div, div);
macro_rules! impl_float_inf_bi_ibig {
($($ty:ty),+; $name:ident, $method_inf:ident, $method_neg_inf:ident, $op:ident) => {
$(impl $name for $ty {
fn $method_inf(&self, other: IBig) -> Fallible<Self> {
let not_finite = || err!(
Overflow,
concat!("({}).", stringify!($method_inf), "({}) is not finite. Consider tightening your parameters."),
self, other);
if !self.is_finite() {
return Err(not_finite());
}
let lhs = FBig::<Up>::try_from(*self)?;
let Ok(output) = catch_unwind_silent(|| lhs.$op(other.clone())) else {
return Err(not_finite())
};
let output = Self::inf_cast(output)?;
output.is_finite().then(|| output).ok_or_else(not_finite)
}
fn $method_neg_inf(&self, other: IBig) -> Fallible<Self> {
let not_finite = || err!(
Overflow,
concat!("({}).", stringify!($method_neg_inf), "({}) is not finite. Consider tightening your parameters."),
self, other);
if !self.is_finite() {
return Err(not_finite());
}
let lhs = FBig::<Down>::try_from(*self)?;
let Ok(output) = catch_unwind_silent(|| lhs.$op(other.clone())) else {
return Err(not_finite())
};
let output = Self::neg_inf_cast(output)?;
output.is_finite().then(|| output).ok_or_else(not_finite)
}
})+
}
}
impl_float_inf_bi_ibig!(f64, f32; InfPowI, inf_powi, neg_inf_powi, powi);