/// Trait exposing various mathematical operations that can be applied using a Decimal. This is only /// present when the `maths` feature has been enabled. pubtrait MathematicalOps { /// The estimated exponential function, e<sup>x</sup>. Stops calculating when it is within /// tolerance of roughly `0.0000002`. fn exp(&self) -> Decimal;
/// The estimated exponential function, e<sup>x</sup>. Stops calculating when it is within /// tolerance of roughly `0.0000002`. Returns `None` on overflow. fn checked_exp(&self) -> Option<Decimal>;
/// The estimated exponential function, e<sup>x</sup> using the `tolerance` provided as a hint /// as to when to stop calculating. A larger tolerance will cause the number to stop calculating /// sooner at the potential cost of a slightly less accurate result. fn exp_with_tolerance(&self, tolerance: Decimal) -> Decimal;
/// The estimated exponential function, e<sup>x</sup> using the `tolerance` provided as a hint /// as to when to stop calculating. A larger tolerance will cause the number to stop calculating /// sooner at the potential cost of a slightly less accurate result. /// Returns `None` on overflow. fn checked_exp_with_tolerance(&self, tolerance: Decimal) -> Option<Decimal>;
/// Raise self to the given integer exponent: x<sup>y</sup> fn powi(&self, exp: i64) -> Decimal;
/// Raise self to the given integer exponent x<sup>y</sup> returning `None` on overflow. fn checked_powi(&self, exp: i64) -> Option<Decimal>;
/// Raise self to the given unsigned integer exponent: x<sup>y</sup> fn powu(&self, exp: u64) -> Decimal;
/// Raise self to the given unsigned integer exponent x<sup>y</sup> returning `None` on overflow. fn checked_powu(&self, exp: u64) -> Option<Decimal>;
/// Raise self to the given floating point exponent: x<sup>y</sup> fn powf(&self, exp: f64) -> Decimal;
/// Raise self to the given floating point exponent x<sup>y</sup> returning `None` on overflow. fn checked_powf(&self, exp: f64) -> Option<Decimal>;
/// Raise self to the given Decimal exponent: x<sup>y</sup>. If `exp` is not whole then the approximation /// e<sup>y*ln(x)</sup> is used. fn powd(&self, exp: Decimal) -> Decimal;
/// Raise self to the given Decimal exponent x<sup>y</sup> returning `None` on overflow. /// If `exp` is not whole then the approximation e<sup>y*ln(x)</sup> is used. fn checked_powd(&self, exp: Decimal) -> Option<Decimal>;
/// The square root of a Decimal. Uses a standard Babylonian method. fn sqrt(&self) -> Option<Decimal>;
/// Calculates the natural logarithm for a Decimal calculated using Taylor's series. fn ln(&self) -> Decimal;
/// Calculates the checked natural logarithm for a Decimal calculated using Taylor's series. /// Returns `None` for negative numbers or zero. fn checked_ln(&self) -> Option<Decimal>;
/// Calculates the base 10 logarithm of a specified Decimal number. fn log10(&self) -> Decimal;
/// Calculates the checked base 10 logarithm of a specified Decimal number. /// Returns `None` for negative numbers or zero. fn checked_log10(&self) -> Option<Decimal>;
letmut term = *self; letmut result = self.checked_add(Decimal::ONE)?;
for factorial in FACTORIAL.iter().skip(2) {
term = self.checked_mul(term)?; let next = result + (term / factorial); let diff = (next - result).abs();
result = next; if diff <= tolerance { break;
}
}
fn checked_powi(&self, exp: i64) -> Option<Decimal> { // For negative exponents we change x^-y into 1 / x^y. // Otherwise, we calculate a standard unsigned exponent if exp >= 0 { returnself.checked_powu(exp as u64);
}
// Get the unsigned exponent let exp = exp.unsigned_abs(); let pow = matchself.checked_powu(exp) {
Some(v) => v,
None => return None,
};
Decimal::ONE.checked_div(pow)
}
fn checked_powu(&self, exp: u64) -> Option<Decimal> { match exp { 0 => Some(Decimal::ONE), 1 => Some(*self), 2 => self.checked_mul(*self),
_ => { // Get the squared value let squared = matchself.checked_mul(*self) {
Some(s) => s,
None => return None,
}; // Square self once and make an infinite sized iterator of the square. let iter = core::iter::repeat(squared);
// We then take half of the exponent to create a finite iterator and then multiply those together. letmut product = Decimal::ONE; for x in iter.take((exp >> 1) as usize) { match product.checked_mul(x) {
Some(r) => product = r,
None => return None,
};
}
// If the exponent is odd we still need to multiply once more if exp & 0x1 > 0 { matchself.checked_mul(product) {
Some(p) => product = p,
None => return None,
}
}
product.normalize_assign();
Some(product)
}
}
}
// If the scale is 0 then it's a trivial calculation let exp = exp.normalize(); if exp.scale() == 0 { if exp.mid() != 0 || exp.hi() != 0 { // Exponent way too big return None;
}
returnif exp.is_sign_negative() { self.checked_powi(-(exp.lo() as i64))
} else { self.checked_powu(exp.lo() as u64)
};
}
// We do some approximations since we've got a decimal exponent. // For positive bases: a^b = exp(b*ln(a)) let negative = self.is_sign_negative(); let e = matchself.abs().ln().checked_mul(exp) {
Some(e) => e,
None => return None,
}; letmut result = e.checked_exp()?;
result.set_sign_negative(negative);
Some(result)
}
// Start with an arbitrary number as the first guess letmut result = self / Decimal::TWO; // Too small to represent, so we start with self // Future iterations could actually avoid using a decimal altogether and use a buffered // vector, only combining back into a decimal on return if result.is_zero() {
result = *self;
} letmut last = result + Decimal::ONE;
// Keep going while the difference is larger than the tolerance letmut circuit_breaker = 0; while last != result {
circuit_breaker += 1;
assert!(circuit_breaker < 1000, "geo mean circuit breaker");
last = result;
result = (result + self / result) / Decimal::TWO;
}
// Approximate using Taylor Series letmut x = *self; letmut count = 0; while x >= Decimal::ONE {
x *= Decimal::E_INVERSE;
count += 1;
} while x <= Decimal::E_INVERSE {
x *= Decimal::E;
count -= 1;
}
x -= Decimal::ONE; if x.is_zero() { return Some(Decimal::new(count, 0));
} letmut result = Decimal::ZERO; letmut iteration = 0; letmut y = Decimal::ONE; letmut last = Decimal::ONE; while last != result && iteration < 100 {
iteration += 1;
last = result;
y *= -x;
result += y / Decimal::new(iteration, 0);
}
Some(Decimal::new(count, 0) - result)
}
// This uses a very basic method for calculating log10. We know the following is true: // log10(n) = ln(n) / ln(10) // From this we can perform some small optimizations: // 1. ln(10) is a constant // 2. Multiplication is faster than division, so we can pre-calculate the constant 1/ln(10) // This allows us to then simplify log10(n) to: // log10(n) = C * ln(n)
// Before doing all of this however, we see if there are simple calculations to be made. let scale = self.scale(); letmut working = self.mantissa_array3();
// Check for scales less than 1 as an early exit if scale > 0 && working[2] == 0 && working[1] == 0 && working[0] == 1 { return Some(Decimal::from_parts(scale, 0, 0, true, 0));
}
// Loop for detecting bordering base 10 values letmut result = 0; letmut base10 = true; while !is_all_zero(&working) { let remainder = div_by_u32(&mut working, 10u32); if remainder != 0 {
base10 = false; break;
}
result += 1; if working[2] == 0 && working[1] == 0 && working[0] == 1 { break;
}
} if base10 { return Some((result - scale as i32).into());
}
fn checked_sin(&self) -> Option<Decimal> { ifself.is_zero() { return Some(Decimal::ZERO);
} ifself.is_sign_negative() { // -Sin(-x) return (-self).checked_sin().map(|x| -x);
} ifself >= &Decimal::TWO_PI { // Reduce large numbers early - we can do this using rem to constrain to a range let adjusted = self.checked_rem(Decimal::TWO_PI)?; return adjusted.checked_sin();
} ifself >= &Decimal::PI { // -Sin(x-π) return (self - Decimal::PI).checked_sin().map(|x| -x);
} ifself >= &Decimal::QUARTER_PI { // Cos(π2-x) return (Decimal::HALF_PI - self).checked_cos();
}
// Taylor series: // ∑(n=0 to ∞) : ((−1)^n / (2n + 1)!) * x^(2n + 1) , x∈R // First few expansions: // x^1/1! - x^3/3! + x^5/5! - x^7/7! + x^9/9! letmut result = Decimal::ZERO; for n in0..TRIG_SERIES_UPPER_BOUND { let x = 2 * n + 1; let element = self.checked_powi(x as i64)?.checked_div(FACTORIAL[x])?; if n & 0x1 == 0 {
result += element;
} else {
result -= element;
}
}
Some(result)
}
fn checked_cos(&self) -> Option<Decimal> { ifself.is_zero() { return Some(Decimal::ONE);
} ifself.is_sign_negative() { // Cos(-x) return (-self).checked_cos();
} ifself >= &Decimal::TWO_PI { // Reduce large numbers early - we can do this using rem to constrain to a range let adjusted = self.checked_rem(Decimal::TWO_PI)?; return adjusted.checked_cos();
} ifself >= &Decimal::PI { // -Cos(x-π) return (self - Decimal::PI).checked_cos().map(|x| -x);
} ifself >= &Decimal::QUARTER_PI { // Sin(π2-x) return (Decimal::HALF_PI - self).checked_sin();
}
// Taylor series: // ∑(n=0 to ∞) : ((−1)^n / (2n)!) * x^(2n) , x∈R // First few expansions: // x^0/0! - x^2/2! + x^4/4! - x^6/6! + x^8/8! letmut result = Decimal::ZERO; for n in0..TRIG_SERIES_UPPER_BOUND { let x = 2 * n; let element = self.checked_powi(x as i64)?.checked_div(FACTORIAL[x])?; if n & 0x1 == 0 {
result += element;
} else {
result -= element;
}
}
Some(result)
}
fn checked_tan(&self) -> Option<Decimal> { ifself.is_zero() { return Some(Decimal::ZERO);
} ifself.is_sign_negative() { // -Tan(-x) return (-self).checked_tan().map(|x| -x);
} ifself >= &Decimal::TWO_PI { // Reduce large numbers early - we can do this using rem to constrain to a range let adjusted = self.checked_rem(Decimal::TWO_PI)?; return adjusted.checked_tan();
} // Reduce to 0 <= x <= PI ifself >= &Decimal::PI { // Tan(x-π) return (self - Decimal::PI).checked_tan();
} // Reduce to 0 <= x <= PI/2 ifself > &Decimal::HALF_PI { // We can use the symmetrical function inside the first quadrant // e.g. tan(x) = -tan((PI/2 - x) + PI/2) return ((Decimal::HALF_PI - self) + Decimal::HALF_PI).checked_tan().map(|x| -x);
}
// It has now been reduced to 0 <= x <= PI/2. If it is >= PI/4 we can make it even smaller // by calculating tan(PI/2 - x) and taking the reciprocal ifself > &Decimal::QUARTER_PI { returnmatch (Decimal::HALF_PI - self).checked_tan() {
Some(x) => Decimal::ONE.checked_div(x),
None => None,
};
}
// Due the way that tan(x) sharply tends towards infinity, we try to optimize // the resulting accuracy by using Trigonometric identity when > PI/8. We do this by // replacing the angle with one that is half as big. ifself > &EIGHTH_PI { // Work out tan(x/2) let tan_half = (self / Decimal::TWO).checked_tan()?; // Work out the dividend i.e. 2tan(x/2) let dividend = Decimal::TWO.checked_mul(tan_half)?;
// Work out the divisor i.e. 1 - tan^2(x/2) let squared = tan_half.checked_mul(tan_half)?; let divisor = Decimal::ONE - squared; // Treat this as infinity if divisor.is_zero() { return None;
} return dividend.checked_div(divisor);
}
// Do a polynomial approximation based upon the Maclaurin series. // This can be simplified to something like: // // ∑(n=1,3,5,7,9)(f(n)(0)/n!)x^n // // First few expansions (which we leverage): // (f'(0)/1!)x^1 + (f'''(0)/3!)x^3 + (f'''''(0)/5!)x^5 + (f'''''''/7!)x^7 // // x + (1/3)x^3 + (2/15)x^5 + (17/315)x^7 + (62/2835)x^9 + (1382/155925)x^11 // // (Generated by https://www.wolframalpha.com/widgets/view.jsp?id=fe1ad8d4f5dbb3cb866d0c89beb527a6) // The more terms, the better the accuracy. This generates accuracy within approx 10^-8 for angles // less than PI/8. const SERIES: [(Decimal, u64); 6] = [ // 1 / 3
(Decimal::from_parts_raw(89478485, 347537611, 180700362, 1835008), 3), // 2 / 15
(Decimal::from_parts_raw(894784853, 3574988881, 72280144, 1835008), 5), // 17 / 315
(Decimal::from_parts_raw(905437054, 3907911371, 2925624, 1769472), 7), // 62 / 2835
(Decimal::from_parts_raw(3191872741, 2108928381, 11855473, 1835008), 9), // 1382 / 155925
(Decimal::from_parts_raw(3482645539, 2612995122, 4804769, 1835008), 11), // 21844 / 6081075
(Decimal::from_parts_raw(4189029078, 2192791200, 1947296, 1835008), 13),
]; letmut result = *self; for (fraction, pow) in SERIES {
result += fraction * self.powu(pow);
}
Some(result)
}
}
impl Pow<Decimal> for Decimal { type Output = Decimal;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.