From b8c56479d100d785e83ec8c6cc809c4565d8367a Mon Sep 17 00:00:00 2001 From: Peter Goodspeed-Niklaus Date: Sun, 10 Sep 2017 20:10:57 +0200 Subject: [PATCH 01/13] Add a new exercise to teach implementing traits. Per the comment [here](https://github.com/exercism/rust/pull/347#issuecomment-326798202), there's continued interest in a Rust-specific exercise focusing on teaching trait implementation. I've got some ideas for how to structure such an exercise, and want to stake a claim to flesh them out. That said, I'm very interested in input as to the specifics of how to go about this. From a255fe8b4f7b4ab82715534a7f88381f476bb280 Mon Sep 17 00:00:00 2001 From: Peter Goodspeed-Niklaus Date: Tue, 12 Sep 2017 14:36:50 +0200 Subject: [PATCH 02/13] Add problem stub - README describing the problem - basic project structure - stub implementation of structs and function interfaces which the tests depend on --- exercises/decimal/.gitignore | 7 +++++ exercises/decimal/Cargo.toml | 6 +++++ exercises/decimal/README.md | 50 ++++++++++++++++++++++++++++++++++++ exercises/decimal/src/lib.rs | 10 ++++++++ 4 files changed, 73 insertions(+) create mode 100644 exercises/decimal/.gitignore create mode 100644 exercises/decimal/Cargo.toml create mode 100644 exercises/decimal/README.md create mode 100644 exercises/decimal/src/lib.rs diff --git a/exercises/decimal/.gitignore b/exercises/decimal/.gitignore new file mode 100644 index 000000000..cb14a4206 --- /dev/null +++ b/exercises/decimal/.gitignore @@ -0,0 +1,7 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock diff --git a/exercises/decimal/Cargo.toml b/exercises/decimal/Cargo.toml new file mode 100644 index 000000000..500e00e4c --- /dev/null +++ b/exercises/decimal/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "decimal" +version = "0.1.0" +authors = ["Peter Goodspeed-Niklaus "] + +[dependencies] diff --git a/exercises/decimal/README.md b/exercises/decimal/README.md new file mode 100644 index 000000000..8ce29e81c --- /dev/null +++ b/exercises/decimal/README.md @@ -0,0 +1,50 @@ +# Decimal + +Implement an arbitrary-precision `Decimal` class. + +Floating point numbers are the most common representation of non-integer real numbers in computing, and they're a common standard defined by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754). They're very flexible and versatile, but they do have some limitations. Famously, in floating point arithmetic, [`0.1 + 0.2 != 0.3`](http://0.30000000000000004.com/). + +The solution to this issue is to find another, lossless way to model arbitrary-precision non-integer reals. This may be less efficient in terms of memory or processing speed than floating point numbers; the goal is to provide exact results. + +Despite `Decimal` being a custom type, we should still be able to treat them as numbers: the `==`, `<`, `>`, `+`, `-`, `*`, and `/` operators should all work as expected on Decimals. + +In Rust, the way to get these operations on custom types is to implement the relevant traits for your custom object. In particular, you'll need to implement at least `PartialEq`, `PartialOrd`, `Add`, `Sub`, `Mul`, and `Div`. Strictly speaking, given that the decimal numbers form a total ordering, you should also implement `Eq` and `Ord`, though those traits are not checked for by these tests. + +## Note + +It would be very easy to implement this exercise by using the [bigdecimal](https://crates.io/crates/bigdecimal) crate. Don't do that; implement this yourself. + +## Hints + +- Instead of implementing arbitrary-precision arithmetic from scratch, consider building your type on top of the [num_bigint](https://crates.io/crates/num-bigint) crate. +- You might be able to [derive](https://doc.rust-lang.org/1.8.0/book/traits.html#deriving) some of the required traits. +- `Decimal` is assumed to be a signed type. You do not have to create a separate unsigned type, though you may do so as an implementation detail if you so choose. + +## Rust Installation + +Refer to the [exercism help page][help-page] for Rust installation and learning resources. + +## Writing the Code + +Execute the tests with: + +```bash +$ cargo test +``` + +All but the first test have been ignored. After you get the first test to pass, remove the ignore flag (`#[ignore]`) from the next test and get the tests to pass again. The test file is located in the `tests` directory. You can also remove the ignore flag from all the tests to get them to run all at once if you wish. + +Make sure to read the [Crates and Modules](https://doc.rust-lang.org/stable/book/crates-and-modules.html) chapter if you haven't already, it will help you with organizing your files. + +## Feedback, Issues, Pull Requests + +The [exercism/rust](https://github.com/exercism/rust) repository on GitHub is the home for all of the Rust exercises. If you have feedback about an exercise, or want to help implement new exercises, head over there and create an issue. Members of the [rust track team](https://github.com/orgs/exercism/teams/rust) are happy to help! + +If you want to know more about Exercism, take a look at the [contribution guide](https://github.com/exercism/docs/blob/master/contributing-to-language-tracks/README.md). + +## Submitting Incomplete Solutions + +It's possible to submit an incomplete solution so you can see how others have completed the exercise. + +[crates-and-modules]: http://doc.rust-lang.org/stable/book/crates-and-modules.html +[help-page]: http://exercism.io/languages/rust diff --git a/exercises/decimal/src/lib.rs b/exercises/decimal/src/lib.rs new file mode 100644 index 000000000..0c2bddc81 --- /dev/null +++ b/exercises/decimal/src/lib.rs @@ -0,0 +1,10 @@ +/// Type implementing arbitrary-precision decimal arithmetic +pub struct Decimal { + // implement your type here +} + +impl Decimal { + pub fn try_from(_: &str) -> Option { + unimplemented!() + } +} From 64d5b3897552fe9d360399dec55a413feeb916ba Mon Sep 17 00:00:00 2001 From: Peter Goodspeed-Niklaus Date: Tue, 12 Sep 2017 17:48:37 +0200 Subject: [PATCH 03/13] Add tests --- exercises/decimal/tests/decimal.rs | 89 ++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 exercises/decimal/tests/decimal.rs diff --git a/exercises/decimal/tests/decimal.rs b/exercises/decimal/tests/decimal.rs new file mode 100644 index 000000000..3d63d74ec --- /dev/null +++ b/exercises/decimal/tests/decimal.rs @@ -0,0 +1,89 @@ +extern crate decimal; +use decimal::Decimal; + +/// 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] +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] +fn test_ne() { + assert!(decimal("0.0") != decimal("1.0")); + assert!(decimal(BIGS[0]) != decimal(BIGS[1])); +} + +#[test] +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] +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] +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] +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] +fn test_mul() { + for big in BIGS.iter() { + assert_eq!(decimal(big) * decimal("2"), decimal(big) + decimal(big)); + } +} + +#[test] +fn test_div() { + for big in BIGS.iter() { + assert_eq!((decimal(big) + decimal(big)) / decimal("2"), decimal(big)); + } +} + +#[test] +fn test_eq_vary_sig_digits() { + assert!(decimal("0") == decimal("0000000000000.0000000000000000000000")); + assert!(decimal("1") == decimal("00000000000000001.000000000000000000")); +} + +#[test] +fn test_add_vary_precision() { + assert_eq!( + decimal("100000000000000000000000000000000000000000000") + + decimal("0.00000000000000000000000000000000000000001"), + decimal(BIGS[0]) + ) +} From 118855b4de020e66866eabcdb0409d4fcf265406 Mon Sep 17 00:00:00 2001 From: Peter Goodspeed-Niklaus Date: Sun, 17 Sep 2017 13:29:41 +0200 Subject: [PATCH 04/13] Update readme and tests --- exercises/decimal/README.md | 4 ++-- exercises/decimal/tests/decimal.rs | 33 +++++++++++++++++++++++------- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/exercises/decimal/README.md b/exercises/decimal/README.md index 8ce29e81c..70f10fd52 100644 --- a/exercises/decimal/README.md +++ b/exercises/decimal/README.md @@ -6,9 +6,9 @@ Floating point numbers are the most common representation of non-integer real nu The solution to this issue is to find another, lossless way to model arbitrary-precision non-integer reals. This may be less efficient in terms of memory or processing speed than floating point numbers; the goal is to provide exact results. -Despite `Decimal` being a custom type, we should still be able to treat them as numbers: the `==`, `<`, `>`, `+`, `-`, `*`, and `/` operators should all work as expected on Decimals. +Despite `Decimal` being a custom type, we should still be able to treat them as numbers: the `==`, `<`, `>`, `+`, `-`, and `*` operators should all work as expected on Decimals. For expediency, you are not required to implement division, as arbitrary-precision division can very easily get out of hand. (How do you represent arbitrary-precision `1/3`?) -In Rust, the way to get these operations on custom types is to implement the relevant traits for your custom object. In particular, you'll need to implement at least `PartialEq`, `PartialOrd`, `Add`, `Sub`, `Mul`, and `Div`. Strictly speaking, given that the decimal numbers form a total ordering, you should also implement `Eq` and `Ord`, though those traits are not checked for by these tests. +In Rust, the way to get these operations on custom types is to implement the relevant traits for your custom object. In particular, you'll need to implement at least `PartialEq`, `PartialOrd`, `Add`, `Sub`, and `Mul`. Strictly speaking, given that the decimal numbers form a total ordering, you should also implement `Eq` and `Ord`, though those traits are not checked for by these tests. ## Note diff --git a/exercises/decimal/tests/decimal.rs b/exercises/decimal/tests/decimal.rs index 3d63d74ec..d9c3d9942 100644 --- a/exercises/decimal/tests/decimal.rs +++ b/exercises/decimal/tests/decimal.rs @@ -66,13 +66,6 @@ fn test_mul() { } } -#[test] -fn test_div() { - for big in BIGS.iter() { - assert_eq!((decimal(big) + decimal(big)) / decimal("2"), decimal(big)); - } -} - #[test] fn test_eq_vary_sig_digits() { assert!(decimal("0") == decimal("0000000000000.0000000000000000000000")); @@ -87,3 +80,29 @@ fn test_add_vary_precision() { decimal(BIGS[0]) ) } + +#[test] +fn test_cleanup_precision() { + assert_eq!( + decimal( + "10000000000000000000000000000000000000000000000.999999999999999999999999998", + ) + + decimal( + "10000000000000000000000000000000000000000000000.000000000000000000000000002", + ), + decimal("20000000000000000000000000000000000000000000001") + ) +} + +#[test] +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] +fn test_explicit_positive() { + assert_eq!(decimal("+1"), decimal("1")); + assert_eq!(decimal("+2.0") - decimal("-0002.0"), decimal("4")); +} From fc5965df2732f5bccfec83ee2756a9878fee86d3 Mon Sep 17 00:00:00 2001 From: Peter Goodspeed-Niklaus Date: Sun, 17 Sep 2017 13:29:58 +0200 Subject: [PATCH 05/13] Add example implementation --- exercises/decimal/Cargo-example.toml | 9 ++ exercises/decimal/example.rs | 187 +++++++++++++++++++++++++++ 2 files changed, 196 insertions(+) create mode 100644 exercises/decimal/Cargo-example.toml create mode 100644 exercises/decimal/example.rs diff --git a/exercises/decimal/Cargo-example.toml b/exercises/decimal/Cargo-example.toml new file mode 100644 index 000000000..e94bc2dd3 --- /dev/null +++ b/exercises/decimal/Cargo-example.toml @@ -0,0 +1,9 @@ +[package] +name = "decimal" +version = "0.1.0" +authors = ["Peter Goodspeed-Niklaus "] + +[dependencies] +try_opt = "0.1.1" +num-bigint = "0.1.40" +num-traits = "0.1.40" diff --git a/exercises/decimal/example.rs b/exercises/decimal/example.rs new file mode 100644 index 000000000..e178021c3 --- /dev/null +++ b/exercises/decimal/example.rs @@ -0,0 +1,187 @@ +use std::cmp::Ordering; +use std::fmt; +use std::ops::{Add, Sub, Mul}; + +#[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 { + // 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::().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 { + 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 alwaye become a decimal") + ); + assert_eq!( + test_str, + Decimal::try_from(test_str) + .expect("This should alwaye become a decimal") + .to_string() + ) + } + } +} From 8eb1869b74514b94312d2cabe4e4e34a4b8d8cec Mon Sep 17 00:00:00 2001 From: Peter Goodspeed-Niklaus Date: Sun, 17 Sep 2017 13:34:42 +0200 Subject: [PATCH 06/13] Update meta elements --- exercises/decimal/.meta/description.md | 21 +++++++++++++++++++++ exercises/decimal/.meta/metadata.yml | 3 +++ exercises/decimal/README.md | 2 +- 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 exercises/decimal/.meta/description.md create mode 100644 exercises/decimal/.meta/metadata.yml diff --git a/exercises/decimal/.meta/description.md b/exercises/decimal/.meta/description.md new file mode 100644 index 000000000..e7bce29c4 --- /dev/null +++ b/exercises/decimal/.meta/description.md @@ -0,0 +1,21 @@ +# Decimal + +Implement an arbitrary-precision `Decimal` class. + +Floating point numbers are the most common representation of non-integer real numbers in computing, and they're a common standard defined by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754). They're very flexible and versatile, but they do have some limitations. Famously, in floating point arithmetic, [`0.1 + 0.2 != 0.3`](http://0.30000000000000004.com/). + +The solution to this issue is to find another, lossless way to model arbitrary-precision non-integer reals. This may be less efficient in terms of memory or processing speed than floating point numbers; the goal is to provide exact results. + +Despite `Decimal` being a custom type, we should still be able to treat them as numbers: the `==`, `<`, `>`, `+`, `-`, and `*` operators should all work as expected on Decimals. For expediency, you are not required to implement division, as arbitrary-precision division can very quickly get out of hand. (How do you represent arbitrary-precision `1/3`?) + +In Rust, the way to get these operations on custom types is to implement the relevant traits for your custom object. In particular, you'll need to implement at least `PartialEq`, `PartialOrd`, `Add`, `Sub`, and `Mul`. Strictly speaking, given that the decimal numbers form a total ordering, you should also implement `Eq` and `Ord`, though those traits are not checked for by these tests. + +## Note + +It would be very easy to implement this exercise by using the [bigdecimal](https://crates.io/crates/bigdecimal) crate. Don't do that; implement this yourself. + +## Hints + +- Instead of implementing arbitrary-precision arithmetic from scratch, consider building your type on top of the [num_bigint](https://crates.io/crates/num-bigint) crate. +- You might be able to [derive](https://doc.rust-lang.org/1.8.0/book/traits.html#deriving) some of the required traits. +- `Decimal` is assumed to be a signed type. You do not have to create a separate unsigned type, though you may do so as an implementation detail if you so choose. diff --git a/exercises/decimal/.meta/metadata.yml b/exercises/decimal/.meta/metadata.yml new file mode 100644 index 000000000..5d95d5694 --- /dev/null +++ b/exercises/decimal/.meta/metadata.yml @@ -0,0 +1,3 @@ +--- +blurb: "Implement a Decimal type" +source: "Peter Goodspeed-Niklaus" diff --git a/exercises/decimal/README.md b/exercises/decimal/README.md index 70f10fd52..bc2ce9a80 100644 --- a/exercises/decimal/README.md +++ b/exercises/decimal/README.md @@ -6,7 +6,7 @@ Floating point numbers are the most common representation of non-integer real nu The solution to this issue is to find another, lossless way to model arbitrary-precision non-integer reals. This may be less efficient in terms of memory or processing speed than floating point numbers; the goal is to provide exact results. -Despite `Decimal` being a custom type, we should still be able to treat them as numbers: the `==`, `<`, `>`, `+`, `-`, and `*` operators should all work as expected on Decimals. For expediency, you are not required to implement division, as arbitrary-precision division can very easily get out of hand. (How do you represent arbitrary-precision `1/3`?) +Despite `Decimal` being a custom type, we should still be able to treat them as numbers: the `==`, `<`, `>`, `+`, `-`, and `*` operators should all work as expected on Decimals. For expediency, you are not required to implement division, as arbitrary-precision division can very quickly get out of hand. (How do you represent arbitrary-precision `1/3`?) In Rust, the way to get these operations on custom types is to implement the relevant traits for your custom object. In particular, you'll need to implement at least `PartialEq`, `PartialOrd`, `Add`, `Sub`, and `Mul`. Strictly speaking, given that the decimal numbers form a total ordering, you should also implement `Eq` and `Ord`, though those traits are not checked for by these tests. From 32f0a4c43049211fb6cb79f5a08a7b0fe5cf2ecb Mon Sep 17 00:00:00 2001 From: Peter Goodspeed-Niklaus Date: Sun, 17 Sep 2017 13:44:45 +0200 Subject: [PATCH 07/13] Update config.json and regen README.md from configlet --- config.json | 14 ++++++++++++++ exercises/decimal/README.md | 25 ++++++++++++++++++------- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/config.json b/config.json index 266223c0b..7970c6a7b 100644 --- a/config.json +++ b/config.json @@ -623,6 +623,20 @@ "topics": [ ] }, + { + "uuid": "7cefed7c-37f4-46c5-9a45-68fe4d0fb326", + "slug": "decimal", + "core": false, + "unlocked_by": null, + "difficulty": 6, + "topics": [ + "struct", + "traits", + "string parsing", + "bigint", + "external crates (optional)" + ] + }, { "uuid": "f3172997-91f5-4941-a76e-91c4b8eed401", "slug": "anagram", diff --git a/exercises/decimal/README.md b/exercises/decimal/README.md index bc2ce9a80..bd007f3a7 100644 --- a/exercises/decimal/README.md +++ b/exercises/decimal/README.md @@ -1,5 +1,7 @@ # Decimal +# Decimal + Implement an arbitrary-precision `Decimal` class. Floating point numbers are the most common representation of non-integer real numbers in computing, and they're a common standard defined by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754). They're very flexible and versatile, but they do have some limitations. Famously, in floating point arithmetic, [`0.1 + 0.2 != 0.3`](http://0.30000000000000004.com/). @@ -22,7 +24,8 @@ It would be very easy to implement this exercise by using the [bigdecimal](https ## Rust Installation -Refer to the [exercism help page][help-page] for Rust installation and learning resources. +Refer to the [exercism help page][help-page] for Rust installation and learning +resources. ## Writing the Code @@ -32,9 +35,14 @@ Execute the tests with: $ cargo test ``` -All but the first test have been ignored. After you get the first test to pass, remove the ignore flag (`#[ignore]`) from the next test and get the tests to pass again. The test file is located in the `tests` directory. You can also remove the ignore flag from all the tests to get them to run all at once if you wish. +All but the first test have been ignored. After you get the first test to +pass, remove the ignore flag (`#[ignore]`) from the next test and get the tests +to pass again. The test file is located in the `tests` directory. You can +also remove the ignore flag from all the tests to get them to run all at once +if you wish. -Make sure to read the [Crates and Modules](https://doc.rust-lang.org/stable/book/crates-and-modules.html) chapter if you haven't already, it will help you with organizing your files. +Make sure to read the [Crates and Modules](https://doc.rust-lang.org/stable/book/crates-and-modules.html) chapter if you +haven't already, it will help you with organizing your files. ## Feedback, Issues, Pull Requests @@ -42,9 +50,12 @@ The [exercism/rust](https://github.com/exercism/rust) repository on GitHub is th If you want to know more about Exercism, take a look at the [contribution guide](https://github.com/exercism/docs/blob/master/contributing-to-language-tracks/README.md). -## Submitting Incomplete Solutions +[help-page]: http://exercism.io/languages/rust +[crates-and-modules]: http://doc.rust-lang.org/stable/book/crates-and-modules.html -It's possible to submit an incomplete solution so you can see how others have completed the exercise. +## Source -[crates-and-modules]: http://doc.rust-lang.org/stable/book/crates-and-modules.html -[help-page]: http://exercism.io/languages/rust +Peter Goodspeed-Niklaus + +## Submitting Incomplete Solutions +It's possible to submit an incomplete solution so you can see how others have completed the exercise. From a21aea92c1bb131d2afd986376d172fc17225d8c Mon Sep 17 00:00:00 2001 From: Peter Goodspeed-Niklaus Date: Sat, 7 Oct 2017 15:10:02 +0200 Subject: [PATCH 08/13] Fix duplicate `# Decimal` header in readme --- exercises/decimal/.meta/description.md | 6 ++---- exercises/decimal/README.md | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/exercises/decimal/.meta/description.md b/exercises/decimal/.meta/description.md index e7bce29c4..e6b0b6708 100644 --- a/exercises/decimal/.meta/description.md +++ b/exercises/decimal/.meta/description.md @@ -1,5 +1,3 @@ -# Decimal - Implement an arbitrary-precision `Decimal` class. Floating point numbers are the most common representation of non-integer real numbers in computing, and they're a common standard defined by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754). They're very flexible and versatile, but they do have some limitations. Famously, in floating point arithmetic, [`0.1 + 0.2 != 0.3`](http://0.30000000000000004.com/). @@ -10,11 +8,11 @@ Despite `Decimal` being a custom type, we should still be able to treat them as In Rust, the way to get these operations on custom types is to implement the relevant traits for your custom object. In particular, you'll need to implement at least `PartialEq`, `PartialOrd`, `Add`, `Sub`, and `Mul`. Strictly speaking, given that the decimal numbers form a total ordering, you should also implement `Eq` and `Ord`, though those traits are not checked for by these tests. -## Note +# Note It would be very easy to implement this exercise by using the [bigdecimal](https://crates.io/crates/bigdecimal) crate. Don't do that; implement this yourself. -## Hints +# Hints - Instead of implementing arbitrary-precision arithmetic from scratch, consider building your type on top of the [num_bigint](https://crates.io/crates/num-bigint) crate. - You might be able to [derive](https://doc.rust-lang.org/1.8.0/book/traits.html#deriving) some of the required traits. diff --git a/exercises/decimal/README.md b/exercises/decimal/README.md index bd007f3a7..4cb75dad0 100644 --- a/exercises/decimal/README.md +++ b/exercises/decimal/README.md @@ -1,7 +1,5 @@ # Decimal -# Decimal - Implement an arbitrary-precision `Decimal` class. Floating point numbers are the most common representation of non-integer real numbers in computing, and they're a common standard defined by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754). They're very flexible and versatile, but they do have some limitations. Famously, in floating point arithmetic, [`0.1 + 0.2 != 0.3`](http://0.30000000000000004.com/). @@ -12,11 +10,11 @@ Despite `Decimal` being a custom type, we should still be able to treat them as In Rust, the way to get these operations on custom types is to implement the relevant traits for your custom object. In particular, you'll need to implement at least `PartialEq`, `PartialOrd`, `Add`, `Sub`, and `Mul`. Strictly speaking, given that the decimal numbers form a total ordering, you should also implement `Eq` and `Ord`, though those traits are not checked for by these tests. -## Note +# Note It would be very easy to implement this exercise by using the [bigdecimal](https://crates.io/crates/bigdecimal) crate. Don't do that; implement this yourself. -## Hints +# Hints - Instead of implementing arbitrary-precision arithmetic from scratch, consider building your type on top of the [num_bigint](https://crates.io/crates/num-bigint) crate. - You might be able to [derive](https://doc.rust-lang.org/1.8.0/book/traits.html#deriving) some of the required traits. From 1a747c5863d405675a31a667c61b1f64b841818e Mon Sep 17 00:00:00 2001 From: Peter Goodspeed-Niklaus Date: Sat, 7 Oct 2017 15:24:50 +0200 Subject: [PATCH 09/13] Add simple tests as suggested From various @petertseng comments in https://github.com/exercism/rust/pull/350 --- exercises/decimal/tests/decimal.rs | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/exercises/decimal/tests/decimal.rs b/exercises/decimal/tests/decimal.rs index d9c3d9942..ca85d2f84 100644 --- a/exercises/decimal/tests/decimal.rs +++ b/exercises/decimal/tests/decimal.rs @@ -25,12 +25,14 @@ fn test_eq() { } #[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])); @@ -39,6 +41,7 @@ fn test_gt() { } #[test] +#[ignore] fn test_lt() { for slice_2 in BIGS.windows(2) { assert!(decimal(slice_2[0]) < decimal(slice_2[1])); @@ -47,6 +50,7 @@ fn test_lt() { } #[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])); @@ -54,12 +58,14 @@ fn test_add() { } #[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)); @@ -67,12 +73,14 @@ fn test_mul() { } #[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") + @@ -82,6 +90,7 @@ fn test_add_vary_precision() { } #[test] +#[ignore] fn test_cleanup_precision() { assert_eq!( decimal( @@ -95,6 +104,7 @@ fn test_cleanup_precision() { } #[test] +#[ignore] fn test_negatives() { assert!(Decimal::try_from("-1").is_some()); assert_eq!(decimal("0") - decimal("1"), decimal("-1")); @@ -102,7 +112,28 @@ fn test_negatives() { } #[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_add_uneven_position() { + assert_eq!(decimal("0.1") + decimal("0.02"), decimal("0.12")); +} + +#[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")); +} From 3bf8f2b705df179db1f2db4e7bcf583332fcae6d Mon Sep 17 00:00:00 2001 From: Peter Goodspeed-Niklaus Date: Wed, 11 Oct 2017 16:27:20 +0200 Subject: [PATCH 10/13] Update README Point to the "latest" version of the version-one book instead of the specific 1.8.0 edition. --- exercises/decimal/.meta/description.md | 2 +- exercises/decimal/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/decimal/.meta/description.md b/exercises/decimal/.meta/description.md index e6b0b6708..f6f313f39 100644 --- a/exercises/decimal/.meta/description.md +++ b/exercises/decimal/.meta/description.md @@ -15,5 +15,5 @@ It would be very easy to implement this exercise by using the [bigdecimal](https # Hints - Instead of implementing arbitrary-precision arithmetic from scratch, consider building your type on top of the [num_bigint](https://crates.io/crates/num-bigint) crate. -- You might be able to [derive](https://doc.rust-lang.org/1.8.0/book/traits.html#deriving) some of the required traits. +- You might be able to [derive](https://doc.rust-lang.org/book/first-edition/traits.html#deriving) some of the required traits. - `Decimal` is assumed to be a signed type. You do not have to create a separate unsigned type, though you may do so as an implementation detail if you so choose. diff --git a/exercises/decimal/README.md b/exercises/decimal/README.md index 4cb75dad0..1644a4b5c 100644 --- a/exercises/decimal/README.md +++ b/exercises/decimal/README.md @@ -17,7 +17,7 @@ It would be very easy to implement this exercise by using the [bigdecimal](https # Hints - Instead of implementing arbitrary-precision arithmetic from scratch, consider building your type on top of the [num_bigint](https://crates.io/crates/num-bigint) crate. -- You might be able to [derive](https://doc.rust-lang.org/1.8.0/book/traits.html#deriving) some of the required traits. +- You might be able to [derive](https://doc.rust-lang.org/book/first-edition/traits.html#deriving) some of the required traits. - `Decimal` is assumed to be a signed type. You do not have to create a separate unsigned type, though you may do so as an implementation detail if you so choose. ## Rust Installation From 0cf66716aa6f272f332f1bbb75a58c16d844bb9e Mon Sep 17 00:00:00 2001 From: Peter Goodspeed-Niklaus Date: Wed, 11 Oct 2017 16:55:10 +0200 Subject: [PATCH 11/13] Add more tests and organize into sections --- exercises/decimal/tests/decimal.rs | 205 ++++++++++++++++++++++++++++- 1 file changed, 199 insertions(+), 6 deletions(-) diff --git a/exercises/decimal/tests/decimal.rs b/exercises/decimal/tests/decimal.rs index ca85d2f84..8ba8b8586 100644 --- a/exercises/decimal/tests/decimal.rs +++ b/exercises/decimal/tests/decimal.rs @@ -15,6 +15,7 @@ const BIGS: [&'static str; 3] = [ "200000000000000000000000000000000000000000000.00000000000000000000000000000000000000003", ]; +// test simple properties of required operations #[test] fn test_eq() { assert!(decimal("0.0") == decimal("0.0")); @@ -72,6 +73,52 @@ fn test_mul() { } } +// 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() { @@ -103,6 +150,33 @@ fn test_cleanup_precision() { ) } +#[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() { @@ -118,12 +192,6 @@ fn test_explicit_positive() { assert_eq!(decimal("+2.0") - decimal("-0002.0"), decimal("4")); } -#[test] -#[ignore] -fn test_add_uneven_position() { - assert_eq!(decimal("0.1") + decimal("0.02"), decimal("0.12")); -} - #[test] #[ignore] fn test_multiply_by_negative() { @@ -137,3 +205,128 @@ 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")) +} From 38e9ff1149275eb1103d393e171487b91b44bd76 Mon Sep 17 00:00:00 2001 From: Peter Tseng Date: Thu, 12 Oct 2017 02:20:16 -0700 Subject: [PATCH 12/13] I'm pretty sure this trailing comma is unnecessary --- exercises/decimal/example.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/decimal/example.rs b/exercises/decimal/example.rs index e178021c3..ed85ca12d 100644 --- a/exercises/decimal/example.rs +++ b/exercises/decimal/example.rs @@ -106,7 +106,7 @@ macro_rules! auto_impl_decimal_ops { fn $func_name(mut self, mut rhs: Self) -> Self { Decimal::equalize_precision(&mut self, &mut rhs); Decimal::new( - $digits_operation(self.digits, rhs.digits,), + $digits_operation(self.digits, rhs.digits), $index_operation(self.decimal_index, rhs.decimal_index), ) } From 6cb369221a3b643e5c22b31368796e1b0592aaad Mon Sep 17 00:00:00 2001 From: Peter Tseng Date: Thu, 12 Oct 2017 02:20:29 -0700 Subject: [PATCH 13/13] spelling: alwaye -> always --- exercises/decimal/example.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/decimal/example.rs b/exercises/decimal/example.rs index ed85ca12d..8819dbced 100644 --- a/exercises/decimal/example.rs +++ b/exercises/decimal/example.rs @@ -174,12 +174,12 @@ mod tests { println!( "Decimal representation of \"{}\": {}", test_str, - Decimal::try_from(test_str).expect("This should alwaye become a decimal") + Decimal::try_from(test_str).expect("This should always become a decimal") ); assert_eq!( test_str, Decimal::try_from(test_str) - .expect("This should alwaye become a decimal") + .expect("This should always become a decimal") .to_string() ) }