Skip to content

Commit 5148295

Browse files
lewisclementErikSchierboom
authored andcommitted
Extract Concepts from v2 exercise decimal
Closes exercism#248 Approached a little different this time now that the core set of concepts start crystallizing. I've taken two reasonably different solutions and extracted applied language components. There's some overlap between some concepts (like `Ordering` and `conditionals`), but I think it's reasonable to touch such concepts in different concept-exercises.
1 parent c31590a commit 5148295

File tree

1 file changed

+305
-0
lines changed

1 file changed

+305
-0
lines changed
Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
# Concepts of Decimal
2+
3+
Decimal explores a lossless alternative for float points values. This highlights the fundamental difference between floating-point arithmetic and the way humans interpret decimal values. A good topic to get right for new programmers.
4+
5+
Some other useful concepts are handled too, like the Traits implemented for primitives.
6+
7+
Variation in approach is very minimal, as the solution doesn't have to be algorithmic-ally complex.
8+
9+
## Extracted concepts
10+
This includes all concepts shown in the examples, and some related concepts not applied here.
11+
12+
**In bold: concept level items**
13+
14+
- **Signed-ness**
15+
- **literals**
16+
- **integers**
17+
- **floats**
18+
- **Floating point characteristics**
19+
- **chars**
20+
- **`u128` and `i128`** types
21+
- **`Option`** type
22+
- `unwrap`
23+
- `unwrap_or`
24+
- **`Result`** type
25+
- **`Self`** type
26+
- **`usize`/`isize`** type
27+
- **`str` and `String`** type
28+
- Difference + `as_str()`
29+
- `trim` and variants
30+
- **Iterator**
31+
- `filter`
32+
- `map`
33+
- `collect`
34+
- `rev`
35+
- `position`
36+
- Related, not applied here: **Generics**
37+
- **Structs**
38+
- **Traits**
39+
- **Ordering**
40+
- **Equality**
41+
- **Debug**
42+
- **Default**
43+
- `From`, `TryFrom` and `FromStr`
44+
- **Number traits**
45+
- Add
46+
- Sub
47+
- Mul
48+
- Div
49+
- `*Assign` variants of above
50+
- Pow
51+
- `powf32`/`powif32` and `powf64`/`powif64`
52+
- **saturating / overflowing / wrapping / checked variants of above**
53+
- **`type` keyword**
54+
- **Deriving**
55+
- **Functions**
56+
- **Methods**
57+
- **Method syntax** (`Type::` vs `self.`)
58+
- **Visibility**
59+
- **References**
60+
- Dereferencing
61+
- **`impl` blocks**
62+
- `new()` - common practice
63+
- **conditionals**
64+
- **if/else if/else**
65+
- `||` and `&&`
66+
- Don't forget **order of operations**!
67+
- **`while` loop**
68+
- **`match`**
69+
- **`if let`/`while let`**
70+
- **variables**
71+
- **`let`/`static`/`const`**
72+
- Casting
73+
- `let x = { //scope }` assign syntax
74+
- **Immutability/explicit mutability**
75+
- **Ownership**
76+
- `to_owned`
77+
- **Tuples**
78+
- **Turbofish operator**
79+
- **`?` operator**
80+
- **explicit early `return`**
81+
- **`use` syntax**
82+
- using multiple modules on single line
83+
- **Opt:** **Crates**
84+
85+
## Example exercise 1
86+
Taken from ["lewisclement"](https://exercism.io/my/solutions/c87d24f45c284c83b0da780d132f1c4b)
87+
```rust
88+
use std::str::FromStr;
89+
use std::ops::{Add, Sub, Mul};
90+
use std::cmp::{max, Ordering};
91+
use num_bigint::BigInt;
92+
use num_traits::pow::Pow;
93+
94+
/// Type implementing arbitrary-precision decimal arithmetic
95+
#[derive(PartialEq, Eq, Ord, Debug)]
96+
pub struct Decimal {
97+
value: BigInt,
98+
point: usize
99+
}
100+
101+
impl PartialOrd for Decimal {
102+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
103+
let (v1, v2) = self.normalize(&other);
104+
105+
Some(v1.cmp(&v2))
106+
}
107+
}
108+
109+
impl Add for Decimal {
110+
type Output = Self;
111+
112+
fn add(self, other: Self) -> Self {
113+
let (v1, v2) = self.normalize(&other);
114+
115+
Decimal::new(v1 + v2, max(self.point, other.point))
116+
}
117+
}
118+
119+
impl Sub for Decimal {
120+
type Output = Self;
121+
122+
fn sub(self, other: Self) -> Self {
123+
let (v1, v2) = self.normalize(&other);
124+
125+
Decimal::new(v1 - v2, max(self.point, other.point))
126+
}
127+
}
128+
129+
impl Mul for Decimal {
130+
type Output = Self;
131+
132+
fn mul(self, other: Self) -> Self {
133+
let (v1, v2) = self.normalize(&other);
134+
135+
Decimal::new(v1 * v2, (self.point + other.point) * 2)
136+
}
137+
}
138+
139+
impl Decimal {
140+
pub fn new(value: BigInt, point: usize) -> Self {
141+
let mut point = point.clone();
142+
let mut value = value.clone();
143+
144+
while &value % 10 == BigInt::from(0) && point > 0 {
145+
value /= 10;
146+
point -= 1;
147+
}
148+
149+
Self {
150+
value,
151+
point
152+
}
153+
}
154+
155+
pub fn try_from(input: &str) -> Option<Decimal> {
156+
let point = input.chars().rev().position(|c| c == '.');
157+
let value = BigInt::from_str(
158+
input.chars().filter(|c| *c != '.').collect::<String>().as_str()
159+
);
160+
161+
if let Ok(value) = value {
162+
Some(Decimal::new(value, point.unwrap_or(0)))
163+
} else {
164+
None
165+
}
166+
}
167+
168+
fn normalize(&self, other: &Self) -> (BigInt, BigInt) {
169+
(
170+
(self.value.clone() * BigInt::from(10).pow(other.point.saturating_sub(self.point) as u32)),
171+
(other.value.clone() * BigInt::from(10).pow(self.point.saturating_sub(other.point) as u32))
172+
)
173+
}
174+
}
175+
```
176+
177+
## Example exercise 2
178+
Taken from ["yawpitch"](https://exercism.io/tracks/rust/exercises/decimal/solutions/16d3747cccca4ee9aeba5a0e8f0ed429)
179+
```rust
180+
use num_bigint::{BigInt, BigUint, Sign, ToBigInt};
181+
use std::cmp::Ordering;
182+
use std::ops::{Add, Mul, Sub};
183+
184+
#[derive(Debug, PartialEq, Eq, Clone, Default)]
185+
pub struct Decimal {
186+
significand: BigInt,
187+
exponent: usize,
188+
}
189+
190+
impl Decimal {
191+
pub fn new(significand: BigInt, exponent: usize) -> Self {
192+
let mut this = Self {
193+
significand,
194+
exponent,
195+
};
196+
this.normalize();
197+
this
198+
}
199+
200+
pub fn try_from(input: &str) -> Option<Self> {
201+
// trim any whitespace padding
202+
let mut input = input.trim();
203+
if input.is_empty() {
204+
return Some(Self::default());
205+
}
206+
207+
let sign = if input.starts_with('-') {
208+
Sign::Minus
209+
} else {
210+
Sign::Plus
211+
};
212+
213+
// trim sign and leading 0s
214+
input = input.trim_start_matches(|c| c == '-' || c == '+' || c == '0');
215+
216+
let mut parts = input.split('.');
217+
let integer = parts.next().unwrap_or("");
218+
let fraction = parts.next().unwrap_or("0");
219+
let parsed = BigUint::parse_bytes((integer.to_owned() + fraction).as_bytes(), 10)?;
220+
let significand = BigInt::from_biguint(sign, parsed);
221+
let exponent = fraction.len();
222+
Some(Self::new(significand, exponent))
223+
}
224+
225+
fn normalize(&mut self) {
226+
let ten = 10.to_bigint().unwrap();
227+
let zero = 0.to_bigint().unwrap();
228+
while self.significand.clone() % ten.clone() == zero && self.exponent > 0 {
229+
self.significand /= ten.clone();
230+
self.exponent -= 1;
231+
}
232+
}
233+
234+
fn promote(&self, exponent: usize) -> BigInt {
235+
let ten = 10.to_bigint().unwrap();
236+
let mut result = self.significand.clone();
237+
238+
match self.exponent.cmp(&exponent) {
239+
Ordering::Equal => result,
240+
Ordering::Less => {
241+
for _ in 0..(exponent - self.exponent) {
242+
result *= ten.clone();
243+
}
244+
result
245+
}
246+
Ordering::Greater => {
247+
for _ in 0..(self.exponent - exponent) {
248+
result /= ten.clone();
249+
}
250+
result
251+
}
252+
}
253+
}
254+
}
255+
256+
impl PartialOrd for Decimal {
257+
fn partial_cmp(&self, other: &Decimal) -> Option<Ordering> {
258+
match self.exponent.cmp(&other.exponent) {
259+
Ordering::Equal => Some(self.significand.cmp(&other.significand)),
260+
Ordering::Less => Some(self.promote(other.exponent).cmp(&other.significand)),
261+
Ordering::Greater => Some(self.significand.cmp(&other.promote(self.exponent))),
262+
}
263+
}
264+
}
265+
266+
impl Add for Decimal {
267+
type Output = Decimal;
268+
269+
fn add(self, other: Decimal) -> Self {
270+
match self.exponent.cmp(&other.exponent) {
271+
Ordering::Equal => Self::new(self.significand + other.significand, self.exponent),
272+
Ordering::Less => Self::new(
273+
self.promote(other.exponent) + other.significand,
274+
other.exponent,
275+
),
276+
Ordering::Greater => Self::new(
277+
self.significand + other.promote(self.exponent),
278+
self.exponent,
279+
),
280+
}
281+
}
282+
}
283+
284+
impl Sub for Decimal {
285+
type Output = Decimal;
286+
287+
fn sub(self, other: Decimal) -> Self {
288+
self.add(Self {
289+
significand: -other.significand,
290+
exponent: other.exponent,
291+
})
292+
}
293+
}
294+
295+
impl Mul for Decimal {
296+
type Output = Decimal;
297+
298+
fn mul(self, other: Decimal) -> Self {
299+
Self::new(
300+
self.significand * other.significand,
301+
self.exponent + other.exponent,
302+
)
303+
}
304+
}
305+
```

0 commit comments

Comments
 (0)