I mentioned this in #30 but figured opening an issue was more appropriate.
Implementation of DivAssign causes issues with trunc_div. I could be wrong, but I believe this is because the DivAssign implementation promotes to float prior to dividing, which causes the i128::MAX to lose some precision.
For example, notice the last digit (3 vs 4):
println!("{}", i128::MAX / 2);
//-> 85070591730234615865843651857942052863
println!("{:?}", Value::SignedBigInt(i128::MAX).trunc_div(2));
//-> UnsignedBigInt(85070591730234615865843651857942052864)
Demo outlining root issue
Click to view demo
println!("\t=============== RAW VALUES ==============\n");
println!("i128::MAX = {}", i128::MAX);
let a = i128::MAX as f64;
println!("i128::MAX as f64 = {a:e} [no fmt] = {a}");
println!("\n\t============== CONVERSION TO f64 PRIOR TO DIVISION ==============\n");
println!("i128::MAX / 2 = {}", i128::MAX / 2);
let b = (i128::MAX as f64) / 2.0;
println!("(i128::MAX as f64) / 2.0 = {b:e} [no fmt] = {b} <-- precision is lost!");
println!("\n\t============== CONVERSION FROM f64 BACK INTO i128 ==============\n");
let c = b as i128;
println!("((i128::MAX as f64) / 2.0) as i128 = {c:e} [no fmt] = {c} <-- loss of precision reflected");
// =============== RAW VALUES ==============
//
// i128::MAX = 170141183460469231731687303715884105727
// i128::MAX as f64 = 1.7014118346046923e38 [no fmt] = 170141183460469230000000000000000000000
//
// ============== CONVERSION TO f64 PRIOR TO DIVISION ==============
//
// i128::MAX / 2 = 85070591730234615865843651857942052863
// (i128::MAX as f64) / 2.0 = 8.507059173023462e37 [no fmt] = 85070591730234620000000000000000000000 <-- precision is lost!
//
// ============== CONVERSION FROM f64 BACK INTO i128 ==============
//
// ((i128::MAX as f64) / 2.0) as i128 = 8.5070591730234615865843651857942052864e37 [no fmt] = 85070591730234615865843651857942052864 <-- loss of precision reflected
Possible fix
Implementation of DivAssign for reference:
Click to view existing DivAssign
impl<Rhs> ops::DivAssign<Rhs> for Value
where
Rhs: Into<Value>,
{
fn div_assign(&mut self, rhs: Rhs) {
self.promote_to_float();
let mut rhs = rhs.into();
rhs.promote_to_float();
*self = dispatch_operation!(self, rhs, n, |rhs| (*n).checked_div(rhs).map(Value::from))
.unwrap_or_else(|| {
self.promote();
dispatch_operation!(self, rhs, n, |rhs| Value::from(*n / rhs))
});
}
}
Changing the implementation to the code below solves the issue described above.
- I didn't want to make this change because I saw comments mentioning that divison always produces a float
- This change would mean all division would follow the same promotion rules as the
AddAssign/SubAssign/MulAssign/RemAssign implementations
- This change also causes a bunch of tests to fail for the same promotional reason (not all divison would produce a float)
Click to view DivAssign fix
impl<Rhs> ops::DivAssign<Rhs> for Value
where
Rhs: Into<Value>,
{
fn div_assign(&mut self, rhs: Rhs) {
let mut rhs = rhs.into();
*self = dispatch_operation!(self, rhs, n, |rhs| (*n).checked_div(rhs).map(Value::from))
.unwrap_or_else(|| {
self.promote();
dispatch_operation!(self, rhs, n, |rhs| Value::from(*n / rhs))
});
}
}
Thoughts?
I mentioned this in #30 but figured opening an issue was more appropriate.
Implementation of
DivAssigncauses issues withtrunc_div. I could be wrong, but I believe this is because theDivAssignimplementation promotes to float prior to dividing, which causes thei128::MAXto lose some precision.For example, notice the last digit (3 vs 4):
Demo outlining root issue
Click to view demo
Possible fix
Implementation of
DivAssignfor reference:Click to view existing DivAssign
Changing the implementation to the code below solves the issue described above.
AddAssign/SubAssign/MulAssign/RemAssignimplementationsClick to view DivAssign fix
Thoughts?