Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,20 @@
"traits"
]
},
{
"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",
Expand Down
7 changes: 7 additions & 0 deletions exercises/decimal/.gitignore
Original file line number Diff line number Diff line change
@@ -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
19 changes: 19 additions & 0 deletions exercises/decimal/.meta/description.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
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/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.
3 changes: 3 additions & 0 deletions exercises/decimal/.meta/metadata.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
blurb: "Implement a Decimal type"
source: "Peter Goodspeed-Niklaus"
9 changes: 9 additions & 0 deletions exercises/decimal/Cargo-example.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "decimal"
version = "0.1.0"
authors = ["Peter Goodspeed-Niklaus <peter.r.goodspeedniklaus@gmail.com>"]

[dependencies]
try_opt = "0.1.1"
num-bigint = "0.1.40"
num-traits = "0.1.40"
6 changes: 6 additions & 0 deletions exercises/decimal/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "decimal"
version = "0.1.0"
authors = ["Peter Goodspeed-Niklaus <peter.r.goodspeedniklaus@gmail.com>"]

[dependencies]
59 changes: 59 additions & 0 deletions exercises/decimal/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# 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/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

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).

[help-page]: http://exercism.io/languages/rust
[crates-and-modules]: http://doc.rust-lang.org/stable/book/crates-and-modules.html

## Source

Peter Goodspeed-Niklaus

## Submitting Incomplete Solutions
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
187 changes: 187 additions & 0 deletions exercises/decimal/example.rs
Original file line number Diff line number Diff line change
@@ -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<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()
)
}
}
}
10 changes: 10 additions & 0 deletions exercises/decimal/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<Decimal> {
unimplemented!()
}
}
Loading