Implement subtraction
This commit is contained in:
parent
2aa53c41dc
commit
35659f4a1a
1
README.md
Normal file
1
README.md
Normal file
@ -0,0 +1 @@
|
||||
https://jvns.ca/blog/2023/01/13/examples-of-floating-point-problems/
|
131
src/lib.rs
131
src/lib.rs
@ -8,6 +8,11 @@ struct Real {
|
||||
decimal_parts: HashMap<i32, i8>
|
||||
}
|
||||
|
||||
struct DecimalSubtraction {
|
||||
carry: bool,
|
||||
values: HashMap<i32, i8>
|
||||
}
|
||||
|
||||
impl Real {
|
||||
pub fn new_int(int_part: i32) -> Real {
|
||||
Real { int_part: int_part, decimal_parts: HashMap::new() }
|
||||
@ -25,8 +30,10 @@ impl Real {
|
||||
Real { int_part: s.parse::<i32>().unwrap(), decimal_parts: HashMap::new() }
|
||||
},
|
||||
Some(idx) => {
|
||||
let int_part = s[0..idx].parse::<i32>().unwrap();
|
||||
dbg!(int_part);
|
||||
let int_part = match &s[0..1] {
|
||||
"-" => {-s[1..idx].parse::<i32>().unwrap()},
|
||||
_ => {s[0..idx].parse::<i32>().unwrap()}
|
||||
};
|
||||
let decimal_string = &s[idx+1..];
|
||||
dbg!(decimal_string);
|
||||
|
||||
@ -52,6 +59,48 @@ impl Real {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cannot put this inside the `Sub impl` because Rust only allows the implementation of the named methods.
|
||||
fn subtract_decimals(minuend: HashMap<i32, i8>, subtrahend: HashMap<i32, i8>) -> DecimalSubtraction {
|
||||
// default to -1 because the 0th index is the first digit after the decimal place
|
||||
let highest_index = *max(minuend.keys().max().unwrap_or(&-1), subtrahend.keys().max().unwrap_or(&-1));
|
||||
if highest_index == -1 as i32 {
|
||||
// i.e. if both decimals are empty
|
||||
return DecimalSubtraction{carry: false, values: HashMap::new()}
|
||||
}
|
||||
|
||||
let mut subtracted_decimal_parts = HashMap::new();
|
||||
let mut should_subtraction_carry_to_integers = false;
|
||||
|
||||
for idx in (0..=highest_index).rev() {
|
||||
// `decimal_part` added, here, because if a previous (i.e. smaller-denomination) subtraction ended up negative,
|
||||
// a `-1` will have been put in the `decimal_part` map.
|
||||
let subtraction = subtracted_decimal_parts.get(&idx).unwrap_or(&0) + minuend.get(&idx).unwrap_or(&0) - subtrahend.get(&idx).unwrap_or(&0);
|
||||
|
||||
match subtraction {
|
||||
x if 0 < x && x <=9 => {
|
||||
subtracted_decimal_parts.insert(idx, subtraction);
|
||||
},
|
||||
x if x < 0 => {
|
||||
subtracted_decimal_parts.insert(idx, 10+subtraction);
|
||||
if idx > 0 {
|
||||
// TODO - hmmm. I should probably actually be using unsigned ints for the values.
|
||||
// This approach would still work, I'd just need a rule that "if there's _any_ value
|
||||
// in the decimal_part slot, subtract 1", rather than blindly adding the value (which
|
||||
// is always -1)
|
||||
subtracted_decimal_parts.insert(idx-1, -1);
|
||||
} else {
|
||||
should_subtraction_carry_to_integers = true;
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
// x == 0 - so clear the idx-th value
|
||||
subtracted_decimal_parts.remove(&idx);
|
||||
}
|
||||
}
|
||||
};
|
||||
DecimalSubtraction { carry: should_subtraction_carry_to_integers, values: subtracted_decimal_parts }
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Real {
|
||||
@ -84,11 +133,46 @@ impl Sub for Real {
|
||||
type Output = Real;
|
||||
|
||||
fn sub(self, other: Real) -> Real {
|
||||
let int_part = self.int_part - other.int_part;
|
||||
Real::new_int(int_part)
|
||||
let int_subtraction = self.int_part - other.int_part;
|
||||
match int_subtraction {
|
||||
x if x > 0 => {
|
||||
let decimal_subtraction = Real::subtract_decimals(self.decimal_parts, other.decimal_parts);
|
||||
Real::new(int_subtraction - (if decimal_subtraction.carry {1} else {0}), decimal_subtraction.values)
|
||||
},
|
||||
x if x < 0 => {
|
||||
let inverted = other - self;
|
||||
Real::new(-inverted.int_part, inverted.decimal_parts)
|
||||
},
|
||||
x if x == 0 => {
|
||||
// Since integer parts are equal, we have to check case-by-case which is larger
|
||||
for idx in 0..max(self.decimal_parts.len(), other.decimal_parts.len()) {
|
||||
// ...just when I think I understand the borrow-checker, this horror-show of casting becomes necessary...
|
||||
if self.decimal_parts.get(&(idx as i32)).unwrap_or(&(0 as i8)) > other.decimal_parts.get(&(idx as i32)).unwrap_or(&(0 as i8)) {
|
||||
let decimal_subtraction = Real::subtract_decimals(self.decimal_parts, other.decimal_parts);
|
||||
// We know that `decimal_subtraction.carry` is false, because the `self.decimal_parts` is larger
|
||||
return Real::new(0, decimal_subtraction.values);
|
||||
}
|
||||
|
||||
if self.decimal_parts.get(&(idx as i32)).unwrap_or(&(0 as i8)) < other.decimal_parts.get(&(idx as i32)).unwrap_or(&(0 as i8)) {
|
||||
return other - self;
|
||||
}
|
||||
|
||||
// and, implicitly - if they are equal, then continue
|
||||
}
|
||||
// Compared all the digits and they're equal, so the original two numbers are equal
|
||||
Real::new(0, HashMap::new())
|
||||
|
||||
},
|
||||
_ => {panic!("int_subtraction is not greater than, lesser than, or equal to 0, but a secret fourth thing.")}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
impl PartialEq for Real {
|
||||
fn eq(&self, other: &Real) -> bool {
|
||||
// And also compare decimals
|
||||
@ -115,6 +199,7 @@ mod tests {
|
||||
assert_eq!(one + two, Real::new_int(3));
|
||||
}
|
||||
|
||||
// TODO - Can I separate these into "test suites" so that each can run as their own function (with, presumably, better reporting?)
|
||||
#[test]
|
||||
fn real_addition() {
|
||||
// Basic equality
|
||||
@ -159,9 +244,45 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subtraction() {
|
||||
fn int_subtraction() {
|
||||
let three = Real::new_int(3);
|
||||
let one = Real::new_int(1);
|
||||
assert_eq!(three - one, Real::new_int(2));
|
||||
}
|
||||
#[test]
|
||||
fn real_subtraction() {
|
||||
// Basic case
|
||||
assert_eq!(
|
||||
Real::from_string("3.45") - Real::from_string("1.23"),
|
||||
Real::from_string("2.22")
|
||||
);
|
||||
|
||||
// Negative result
|
||||
assert_eq!(
|
||||
Real::from_string("1.23") - Real::from_string("3.45"),
|
||||
Real::from_string("-2.22")
|
||||
);
|
||||
|
||||
// Equality
|
||||
assert_eq!(
|
||||
Real::from_string("1.23") - Real::from_string("1.23"),
|
||||
Real::from_string("0")
|
||||
);
|
||||
|
||||
// Carry
|
||||
assert_eq!(
|
||||
Real::from_string("1.11") - Real::from_string("1.02"),
|
||||
Real::from_string("0.09")
|
||||
);
|
||||
|
||||
// Carry into integers
|
||||
assert_eq!(
|
||||
Real::from_string("1.0") - Real::from_string("0.1"),
|
||||
Real::from_string("0.9")
|
||||
)
|
||||
|
||||
// Multiple carry
|
||||
}
|
||||
|
||||
// TODO - test for optimization, don't keep unnecessary 0's!
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user