Decimal
1. Readme
十进制
实现任意精度的Decimal
类。
浮点数是计算中非整数实数的最常见表示,它们是由IEEE 754标准定义。它们非常灵活且通用,但它们确实有一些局限性。众所周知,在浮点运算中,0.1 + 0.2 != 0.3
。
解决这一问题的方法是,寻找另一种无损的方法来模拟任意精度的非整数 实数。这可能在内存或处理速度方面,不如浮点数有效;但目标是提供准确的结果。
尽管Decimal
作为一种自定义类型,我们仍然应该能够将它们视为数字: 而==
,<
,>
,+
,-
和*
操作符都应该按小数进行工作。只是权宜之计,你不需要执行除法,因为任意的精确除法很快就会失控。(如何表示任意精度1/3
?)
在 Rust 中,将这些操作用于自定义类型的方法是,实现自定义对象的相关 trait。特别是,您至少需要实现.PartialEq
,PartialOrd
,Add
,Sub
和Mul
。 严格地说,由于十进制数构成一个总排序,你也应该实现Eq
和Ord
,尽管这些 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() ) } } } #}