Decimal

1. Readme

十进制

实现任意精度的Decimal类。

浮点数是计算中非整数实数的最常见表示,它们是由IEEE 754标准定义。它们非常灵活且通用,但它们确实有一些局限性。众所周知,在浮点运算中,0.1 + 0.2 != 0.3

解决这一问题的方法是,寻找另一种无损的方法来模拟任意精度的非整数 实数。这可能在内存或处理速度方面,不如浮点数有效;但目标是提供准确的结果。

尽管Decimal作为一种自定义类型,我们仍然应该能够将它们视为数字: 而==<>+-*操作符都应该按小数进行工作。只是权宜之计,你不需要执行除法,因为任意的精确除法很快就会失控。(如何表示任意精度1/3?)

在 Rust 中,将这些操作用于自定义类型的方法是,实现自定义对象的相关 trait。特别是,您至少需要实现.PartialEqPartialOrdAddSubMul。 严格地说,由于十进制数构成一个总排序,你也应该实现EqOrd,尽管这些 trait 并没有被这些测试所检验.

笔记

使用这种.bigdecimal箱子方法很容易实现这个练习。但不要那样做,你自己来实现。

提示

  • 不要从头开始执行任意精确的算术,而是考虑在num_bigint箱子之上构建自己的类。
  • 你也许能derive一些需要的 trait。
  • Decimal假设为符号类型。你不必创建一个单独的无符号类型,尽管你可以这样做,如果你选择这么样,能作为一个实现细节。

资源

Peter Goodspeed-Niklaus

2. 开始你的表演

/// Type implementing arbitrary-precision decimal arithmetic
pub struct Decimal {
   // implement your type here
}

impl Decimal {
   pub fn try_from(input: &str) -> Option<Decimal> {
       unimplemented!("Create a new decimal with a value of {}", input)
   }
}

3. 测试代码查看


# #![allow(unused_variables)]
#fn main() {
/// Create a Decimal from a string literal
///
/// Use only when you _know_ that your value is valid.
fn decimal(input: &str) -> Decimal {
   Decimal::try_from(input).expect("That was supposed to be a valid value")
}

/// Some big and precise values we can use for testing. [0] + [1] == [2]
const BIGS: [&'static str; 3] = [
   "100000000000000000000000000000000000000000000.00000000000000000000000000000000000000001",
   "100000000000000000000000000000000000000000000.00000000000000000000000000000000000000002",
   "200000000000000000000000000000000000000000000.00000000000000000000000000000000000000003",
];

// test simple properties of required operations
#[test]
fn test_eq() {
   assert!(decimal("0.0") == decimal("0.0"));
   assert!(decimal("1.0") == decimal("1.0"));
   for big in BIGS.iter() {
       assert!(decimal(big) == decimal(big));
   }
}

#[test]
//#[ignore]
fn test_ne() {
   assert!(decimal("0.0") != decimal("1.0"));
   assert!(decimal(BIGS[0]) != decimal(BIGS[1]));
}

#[test]
//#[ignore]
fn test_gt() {
   for slice_2 in BIGS.windows(2) {
       assert!(decimal(slice_2[1]) > decimal(slice_2[0]));
       assert!(!(decimal(slice_2[0]) > decimal(slice_2[1])));
   }
}

#[test]
//#[ignore]
fn test_lt() {
   for slice_2 in BIGS.windows(2) {
       assert!(decimal(slice_2[0]) < decimal(slice_2[1]));
       assert!(!(decimal(slice_2[1]) < decimal(slice_2[0])));
   }
}

#[test]
//#[ignore]
fn test_add() {
   assert_eq!(decimal("0.1") + decimal("0.2"), decimal("0.3"));
   assert_eq!(decimal(BIGS[0]) + decimal(BIGS[1]), decimal(BIGS[2]));
   assert_eq!(decimal(BIGS[1]) + decimal(BIGS[0]), decimal(BIGS[2]));
}

#[test]
//#[ignore]
fn test_sub() {
   assert_eq!(decimal(BIGS[2]) - decimal(BIGS[1]), decimal(BIGS[0]));
   assert_eq!(decimal(BIGS[2]) - decimal(BIGS[0]), decimal(BIGS[1]));
}

#[test]
//#[ignore]
fn test_mul() {
   for big in BIGS.iter() {
       assert_eq!(decimal(big) * decimal("2"), decimal(big) + decimal(big));
   }
}

// test identities
#[test]
//#[ignore]
fn test_add_id() {
   assert_eq!(decimal("1.0") + decimal("0.0"), decimal("1.0"));
   assert_eq!(decimal("0.1") + decimal("0.0"), decimal("0.1"));
   assert_eq!(decimal("0.0") + decimal("1.0"), decimal("1.0"));
   assert_eq!(decimal("0.0") + decimal("0.1"), decimal("0.1"));
}

#[test]
//#[ignore]
fn test_sub_id() {
   assert_eq!(decimal("1.0") - decimal("0.0"), decimal("1.0"));
   assert_eq!(decimal("0.1") - decimal("0.0"), decimal("0.1"));
}

#[test]
//#[ignore]
fn test_mul_id() {
   assert_eq!(decimal("2.1") * decimal("1.0"), decimal("2.1"));
   assert_eq!(decimal("1.0") * decimal("2.1"), decimal("2.1"));
}

#[test]
//#[ignore]
fn test_gt_positive_and_zero() {
   assert!(decimal("1.0") > decimal("0.0"));
   assert!(decimal("0.1") > decimal("0.0"));
}

#[test]
//#[ignore]
fn test_gt_negative_and_zero() {
   assert!(decimal("0.0") > decimal("-0.1"));
   assert!(decimal("0.0") > decimal("-1.0"));
}

// tests of arbitrary precision behavior
#[test]
//#[ignore]
fn test_add_uneven_position() {
   assert_eq!(decimal("0.1") + decimal("0.02"), decimal("0.12"));
}

#[test]
//#[ignore]
fn test_eq_vary_sig_digits() {
   assert!(decimal("0") == decimal("0000000000000.0000000000000000000000"));
   assert!(decimal("1") == decimal("00000000000000001.000000000000000000"));
}

#[test]
//#[ignore]
fn test_add_vary_precision() {
   assert_eq!(
       decimal("100000000000000000000000000000000000000000000")
           + decimal("0.00000000000000000000000000000000000000001"),
       decimal(BIGS[0])
   )
}

#[test]
//#[ignore]
fn test_cleanup_precision() {
   assert_eq!(
       decimal("10000000000000000000000000000000000000000000000.999999999999999999999999998",)
           + decimal(
               "10000000000000000000000000000000000000000000000.000000000000000000000000002",
           ),
       decimal("20000000000000000000000000000000000000000000001")
   )
}

#[test]
//#[ignore]
fn test_gt_varying_positive_precisions() {
   assert!(decimal("1.1") > decimal("1.01"));
   assert!(decimal("1.01") > decimal("1.0"));
   assert!(decimal("1.0") > decimal("0.1"));
   assert!(decimal("0.1") > decimal("0.01"));
}

#[test]
//#[ignore]
fn test_gt_positive_and_negative() {
   assert!(decimal("1.0") > decimal("-1.0"));
   assert!(decimal("1.1") > decimal("-1.1"));
   assert!(decimal("0.1") > decimal("-0.1"));
}

#[test]
//#[ignore]
fn test_gt_varying_negative_precisions() {
   assert!(decimal("-0.01") > decimal("-0.1"));
   assert!(decimal("-0.1") > decimal("-1.0"));
   assert!(decimal("-1.0") > decimal("-1.01"));
   assert!(decimal("-1.01") > decimal("-1.1"));
}

// test signed properties
#[test]
//#[ignore]
fn test_negatives() {
   assert!(Decimal::try_from("-1").is_some());
   assert_eq!(decimal("0") - decimal("1"), decimal("-1"));
   assert_eq!(decimal("5.5") + decimal("-6.5"), decimal("-1"));
}

#[test]
//#[ignore]
fn test_explicit_positive() {
   assert_eq!(decimal("+1"), decimal("1"));
   assert_eq!(decimal("+2.0") - decimal("-0002.0"), decimal("4"));
}

#[test]
//#[ignore]
fn test_multiply_by_negative() {
   assert_eq!(decimal("5") * decimal("-0.2"), decimal("-1"));
   assert_eq!(decimal("-20") * decimal("-0.2"), decimal("4"));
}

#[test]
//#[ignore]
fn test_simple_partial_cmp() {
   assert!(decimal("1.0") < decimal("1.1"));
   assert!(decimal("0.00000000000000000000001") > decimal("-20000000000000000000000000000"));
}

// test carrying rules
// these tests are designed to ensure correctness of implementations for which the
// integer and fractional parts of the number are stored separately
#[test]
//#[ignore]
fn test_carry_into_integer() {
   assert_eq!(decimal("0.901") + decimal("0.1"), decimal("1.001"))
}

#[test]
//#[ignore]
fn test_carry_into_fractional_with_digits_to_right() {
   assert_eq!(decimal("0.0901") + decimal("0.01"), decimal("0.1001"))
}

#[test]
//#[ignore]
fn test_add_carry_over_negative() {
   assert_eq!(decimal("-1.99") + decimal("-0.01"), decimal("-2.0"))
}

#[test]
//#[ignore]
fn test_sub_carry_over_negative() {
   assert_eq!(decimal("-1.99") - decimal("0.01"), decimal("-2.0"))
}

#[test]
//#[ignore]
fn test_add_carry_over_negative_with_fractional() {
   assert_eq!(decimal("-1.99") + decimal("-0.02"), decimal("-2.01"))
}

#[test]
//#[ignore]
fn test_sub_carry_over_negative_with_fractional() {
   assert_eq!(decimal("-1.99") - decimal("0.02"), decimal("-2.01"))
}

#[test]
//#[ignore]
fn test_carry_from_rightmost_one() {
   assert_eq!(decimal("0.09") + decimal("0.01"), decimal("0.1"))
}

#[test]
//#[ignore]
fn test_carry_from_rightmost_more() {
   assert_eq!(decimal("0.099") + decimal("0.001"), decimal("0.1"))
}

#[test]
//#[ignore]
fn test_carry_from_rightmost_into_integer() {
   assert_eq!(decimal("0.999") + decimal("0.001"), decimal("1.0"))
}

// test arithmetic borrow rules
#[test]
//#[ignore]
fn test_add_borrow() {
   assert_eq!(decimal("0.01") + decimal("-0.0001"), decimal("0.0099"))
}

#[test]
//#[ignore]
fn test_sub_borrow() {
   assert_eq!(decimal("0.01") - decimal("0.0001"), decimal("0.0099"))
}

#[test]
//#[ignore]
fn test_add_borrow_integral() {
   assert_eq!(decimal("1.0") + decimal("-0.01"), decimal("0.99"))
}

#[test]
//#[ignore]
fn test_sub_borrow_integral() {
   assert_eq!(decimal("1.0") - decimal("0.01"), decimal("0.99"))
}

#[test]
//#[ignore]
fn test_add_borrow_integral_zeroes() {
   assert_eq!(decimal("1.0") + decimal("-0.99"), decimal("0.01"))
}

#[test]
//#[ignore]
fn test_sub_borrow_integral_zeroes() {
   assert_eq!(decimal("1.0") - decimal("0.99"), decimal("0.01"))
}

#[test]
//#[ignore]
fn test_borrow_from_negative() {
   assert_eq!(decimal("-1.0") + decimal("0.01"), decimal("-0.99"))
}

#[test]
//#[ignore]
fn test_add_into_fewer_digits() {
   assert_eq!(decimal("0.011") + decimal("-0.001"), decimal("0.01"))
}

// misc tests of arithmetic properties
#[test]
//#[ignore]
fn test_sub_into_fewer_digits() {
   assert_eq!(decimal("0.011") - decimal("0.001"), decimal("0.01"))
}

#[test]
//#[ignore]
fn test_add_away_decimal() {
   assert_eq!(decimal("1.1") + decimal("-0.1"), decimal("1.0"))
}

#[test]
//#[ignore]
fn test_sub_away_decimal() {
   assert_eq!(decimal("1.1") - decimal("0.1"), decimal("1.0"))
}

#}

4. 答案


# #![allow(unused_variables)]
#fn main() {
use std::cmp::Ordering;
use std::fmt;
use std::ops::{Add, Mul, Sub};

#[macro_use]
extern crate try_opt;

extern crate num_bigint;
use num_bigint::BigInt;
extern crate num_traits;
use num_traits::pow;

/// Type implementing arbitrary-precision decimal arithmetic
#[derive(Debug, Eq, Clone)]
pub struct Decimal {
   digits: BigInt,
   decimal_index: usize,
}

impl Decimal {
   fn new(digits: BigInt, decimal_index: usize) -> Decimal {
       let mut value = Decimal {
           digits: digits,
           decimal_index: decimal_index,
       };
       value.reduce();
       value
   }

   pub fn try_from(mut input: &str) -> Option<Decimal> {
       // clear extraneous whitespace
       input = input.trim();

       // don't bother to trim extraneous zeroes
       // leave it to users to manage their own memory

       // now build a representation of the number to parse
       let mut digits = String::with_capacity(input.len());
       let mut decimal_index = None;
       for ch in input.chars() {
           match ch {
               '0'...'9' | '-' | '+' => {
                   digits.push(ch);
                   if let Some(idx) = decimal_index.as_mut() {
                       *idx += 1;
                   }
               }
               '.' => {
                   if decimal_index.is_some() {
                       return None;
                   }
                   decimal_index = Some(0)
               }
               _ => return None,
           }
       }
       Some(Decimal::new(
           try_opt!(digits.parse::<BigInt>().ok()),
           match decimal_index {
               Some(idx) => idx,
               None => 0,
           },
       ))
   }

   /// Add precision to the less-precise value until precisions match
   ///
   /// Precision, in this case, is defined as the decimal index.
   fn equalize_precision(mut one: &mut Decimal, mut two: &mut Decimal) {
       fn expand(lower_precision: &mut Decimal, higher_precision: &Decimal) {
           let precision_difference =
               (higher_precision.decimal_index - lower_precision.decimal_index) as usize;

           lower_precision.digits =
               &lower_precision.digits * pow(BigInt::from(10_usize), precision_difference);
           lower_precision.decimal_index += precision_difference;
       }
       if one.decimal_index < two.decimal_index {
           expand(&mut one, &two)
       } else if one.decimal_index > two.decimal_index {
           expand(&mut two, &one)
       }
       assert_eq!(one.decimal_index, two.decimal_index);
   }

   /// Eliminate extraneous trailing zeroes
   ///
   /// This reduces the decimal index, so that the raw values are easier to parse
   fn reduce(&mut self) {
       let extra_zeroes = self.digits
           .to_string() // produce a decimal representation
           .chars()
           .rev() // trailing values
           .take(self.decimal_index) // not counting past the decimal point
           .take_while(|&c| c == '0') // counting only `0` digits
           .count();
       self.digits = &self.digits / pow(BigInt::from(10_usize), extra_zeroes);
       self.decimal_index -= extra_zeroes;
   }
}

macro_rules! auto_impl_decimal_ops {
   ($trait:ident, $func_name:ident, $digits_operation:expr, $index_operation:expr) => {
       impl $trait for Decimal {
           type Output = Self;
           fn $func_name(mut self, mut rhs: Self) -> Self {
               Decimal::equalize_precision(&mut self, &mut rhs);
               Decimal::new(
                   $digits_operation(self.digits, rhs.digits),
                   $index_operation(self.decimal_index, rhs.decimal_index),
               )
           }
       }
   };
}

auto_impl_decimal_ops!(Add, add, |s, o| s + o, |s, _| s);
auto_impl_decimal_ops!(Sub, sub, |s, o| s - o, |s, _| s);
auto_impl_decimal_ops!(Mul, mul, |s, o| s * o, |s, o| s + o);

macro_rules! auto_impl_decimal_cow {
   ($trait:ident, $func_name:ident, $digits_operation:expr, $return_type:ty) => {
       impl $trait for Decimal {
           fn $func_name(&self, other: &Self) -> $return_type {
               if self.decimal_index == other.decimal_index {
                   $digits_operation(&self.digits, &other.digits)
               } else {
                   // if we're here, the decimal indexes are unmatched.
                   // We have to compare equal terms.
                   // clone both sides so we can modify either of them as necessary.
                   // not efficient, but whatever.
                   let mut one = self.clone();
                   let mut two = other.clone();
                   Decimal::equalize_precision(&mut one, &mut two);
                   one.$func_name(&two)
               }
           }
       }
   };
}

auto_impl_decimal_cow!(PartialEq, eq, |a, b| a == b, bool);
auto_impl_decimal_cow!(Ord, cmp, |a: &BigInt, b: &BigInt| a.cmp(b), Ordering);

impl PartialOrd for Decimal {
   fn partial_cmp(&self, other: &Decimal) -> Option<Ordering> {
       Some(self.cmp(other))
   }
}

impl fmt::Display for Decimal {
   fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
       // get a representation of the pure digits,
       // left-padded with zeroes
       let digits = format!("{:0>width$}", self.digits, width = self.decimal_index);
       if self.decimal_index == digits.len() {
           write!(f, "0.{}", digits)
       } else if self.decimal_index == 0 {
           write!(f, "{}", digits)
       } else {
           let (before_index, after_index) = digits.split_at(digits.len() - self.decimal_index);
           write!(f, "{}.{}", before_index, after_index)
       }
   }
}

#[cfg(test)]
mod tests {
   use super::*;

   #[test]
   fn test_display_temp() {
       for test_str in vec!["0", "1", "20", "0.3", "0.04", "50.05", "66.0006", "0.007"] {
           println!(
               "Decimal representation of \"{}\": {}",
               test_str,
               Decimal::try_from(test_str).expect("This should always become a decimal")
           );
           assert_eq!(
               test_str,
               Decimal::try_from(test_str)
                   .expect("This should always become a decimal")
                   .to_string()
           )
       }
   }
}

#}



填充/相关