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>
|
decimal_parts: HashMap<i32, i8>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct DecimalSubtraction {
|
||||||
|
carry: bool,
|
||||||
|
values: HashMap<i32, i8>
|
||||||
|
}
|
||||||
|
|
||||||
impl Real {
|
impl Real {
|
||||||
pub fn new_int(int_part: i32) -> Real {
|
pub fn new_int(int_part: i32) -> Real {
|
||||||
Real { int_part: int_part, decimal_parts: HashMap::new() }
|
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() }
|
Real { int_part: s.parse::<i32>().unwrap(), decimal_parts: HashMap::new() }
|
||||||
},
|
},
|
||||||
Some(idx) => {
|
Some(idx) => {
|
||||||
let int_part = s[0..idx].parse::<i32>().unwrap();
|
let int_part = match &s[0..1] {
|
||||||
dbg!(int_part);
|
"-" => {-s[1..idx].parse::<i32>().unwrap()},
|
||||||
|
_ => {s[0..idx].parse::<i32>().unwrap()}
|
||||||
|
};
|
||||||
let decimal_string = &s[idx+1..];
|
let decimal_string = &s[idx+1..];
|
||||||
dbg!(decimal_string);
|
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 {
|
impl Add for Real {
|
||||||
@ -84,11 +133,46 @@ impl Sub for Real {
|
|||||||
type Output = Real;
|
type Output = Real;
|
||||||
|
|
||||||
fn sub(self, other: Real) -> Real {
|
fn sub(self, other: Real) -> Real {
|
||||||
let int_part = self.int_part - other.int_part;
|
let int_subtraction = self.int_part - other.int_part;
|
||||||
Real::new_int(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 {
|
impl PartialEq for Real {
|
||||||
fn eq(&self, other: &Real) -> bool {
|
fn eq(&self, other: &Real) -> bool {
|
||||||
// And also compare decimals
|
// And also compare decimals
|
||||||
@ -115,6 +199,7 @@ mod tests {
|
|||||||
assert_eq!(one + two, Real::new_int(3));
|
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]
|
#[test]
|
||||||
fn real_addition() {
|
fn real_addition() {
|
||||||
// Basic equality
|
// Basic equality
|
||||||
@ -159,9 +244,45 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn subtraction() {
|
fn int_subtraction() {
|
||||||
let three = Real::new_int(3);
|
let three = Real::new_int(3);
|
||||||
let one = Real::new_int(1);
|
let one = Real::new_int(1);
|
||||||
assert_eq!(three - one, Real::new_int(2));
|
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