Revision control

Copy as Markdown

Other Tools

use crate::{
constants::{MAX_PRECISION_U32, POWERS_10, U32_MASK},
decimal::{CalculationResult, Decimal},
ops::array::{
add_by_internal, cmp_internal, div_by_u32, is_all_zero, mul_by_u32, mul_part, rescale_internal, shl1_internal,
},
};
use core::cmp::Ordering;
use num_traits::Zero;
pub(crate) fn add_impl(d1: &Decimal, d2: &Decimal) -> CalculationResult {
// Convert to the same scale
let mut my = d1.mantissa_array3();
let mut my_scale = d1.scale();
let mut ot = d2.mantissa_array3();
let mut other_scale = d2.scale();
rescale_to_maximum_scale(&mut my, &mut my_scale, &mut ot, &mut other_scale);
let mut final_scale = my_scale.max(other_scale);
// Add the items together
let my_negative = d1.is_sign_negative();
let other_negative = d2.is_sign_negative();
let mut negative = false;
let carry;
if !(my_negative ^ other_negative) {
negative = my_negative;
carry = add_by_internal3(&mut my, &ot);
} else {
let cmp = cmp_internal(&my, &ot);
// -x + y
// if x > y then it's negative (i.e. -2 + 1)
match cmp {
Ordering::Less => {
negative = other_negative;
sub_by_internal3(&mut ot, &my);
my[0] = ot[0];
my[1] = ot[1];
my[2] = ot[2];
}
Ordering::Greater => {
negative = my_negative;
sub_by_internal3(&mut my, &ot);
}
Ordering::Equal => {
// -2 + 2
my[0] = 0;
my[1] = 0;
my[2] = 0;
}
}
carry = 0;
}
// If we have a carry we underflowed.
// We need to lose some significant digits (if possible)
if carry > 0 {
if final_scale == 0 {
return CalculationResult::Overflow;
}
// Copy it over to a temp array for modification
let mut temp = [my[0], my[1], my[2], carry];
while final_scale > 0 && temp[3] != 0 {
div_by_u32(&mut temp, 10);
final_scale -= 1;
}
// If we still have a carry bit then we overflowed
if temp[3] > 0 {
return CalculationResult::Overflow;
}
// Copy it back - we're done
my[0] = temp[0];
my[1] = temp[1];
my[2] = temp[2];
}
CalculationResult::Ok(Decimal::from_parts(my[0], my[1], my[2], negative, final_scale))
}
pub(crate) fn sub_impl(d1: &Decimal, d2: &Decimal) -> CalculationResult {
add_impl(d1, &(-*d2))
}
pub(crate) fn div_impl(d1: &Decimal, d2: &Decimal) -> CalculationResult {
if d2.is_zero() {
return CalculationResult::DivByZero;
}
if d1.is_zero() {
return CalculationResult::Ok(Decimal::zero());
}
let dividend = d1.mantissa_array3();
let divisor = d2.mantissa_array3();
let mut quotient = [0u32, 0u32, 0u32];
let mut quotient_scale: i32 = d1.scale() as i32 - d2.scale() as i32;
// We supply an extra overflow word for each of the dividend and the remainder
let mut working_quotient = [dividend[0], dividend[1], dividend[2], 0u32];
let mut working_remainder = [0u32, 0u32, 0u32, 0u32];
let mut working_scale = quotient_scale;
let mut remainder_scale = quotient_scale;
let mut underflow;
loop {
div_internal(&mut working_quotient, &mut working_remainder, &divisor);
underflow = add_with_scale_internal(
&mut quotient,
&mut quotient_scale,
&mut working_quotient,
&mut working_scale,
);
// Multiply the remainder by 10
let mut overflow = 0;
for part in working_remainder.iter_mut() {
let (lo, hi) = mul_part(*part, 10, overflow);
*part = lo;
overflow = hi;
}
// Copy temp remainder into the temp quotient section
working_quotient.copy_from_slice(&working_remainder);
remainder_scale += 1;
working_scale = remainder_scale;
if underflow || is_all_zero(&working_remainder) {
break;
}
}
// If we have a really big number try to adjust the scale to 0
while quotient_scale < 0 {
copy_array_diff_lengths(&mut working_quotient, &quotient);
working_quotient[3] = 0;
working_remainder.iter_mut().for_each(|x| *x = 0);
// Mul 10
let mut overflow = 0;
for part in &mut working_quotient {
let (lo, hi) = mul_part(*part, 10, overflow);
*part = lo;
overflow = hi;
}
for part in &mut working_remainder {
let (lo, hi) = mul_part(*part, 10, overflow);
*part = lo;
overflow = hi;
}
if working_quotient[3] == 0 && is_all_zero(&working_remainder) {
quotient_scale += 1;
quotient[0] = working_quotient[0];
quotient[1] = working_quotient[1];
quotient[2] = working_quotient[2];
} else {
// Overflow
return CalculationResult::Overflow;
}
}
if quotient_scale > 255 {
quotient[0] = 0;
quotient[1] = 0;
quotient[2] = 0;
quotient_scale = 0;
}
let mut quotient_negative = d1.is_sign_negative() ^ d2.is_sign_negative();
// Check for underflow
let mut final_scale: u32 = quotient_scale as u32;
if final_scale > MAX_PRECISION_U32 {
let mut remainder = 0;
// Division underflowed. We must remove some significant digits over using
// an invalid scale.
while final_scale > MAX_PRECISION_U32 && !is_all_zero(&quotient) {
remainder = div_by_u32(&mut quotient, 10);
final_scale -= 1;
}
if final_scale > MAX_PRECISION_U32 {
// Result underflowed so set to zero
final_scale = 0;
quotient_negative = false;
} else if remainder >= 5 {
for part in &mut quotient {
if remainder == 0 {
break;
}
let digit: u64 = u64::from(*part) + 1;
remainder = if digit > 0xFFFF_FFFF { 1 } else { 0 };
*part = (digit & 0xFFFF_FFFF) as u32;
}
}
}
CalculationResult::Ok(Decimal::from_parts(
quotient[0],
quotient[1],
quotient[2],
quotient_negative,
final_scale,
))
}
pub(crate) fn mul_impl(d1: &Decimal, d2: &Decimal) -> CalculationResult {
// Early exit if either is zero
if d1.is_zero() || d2.is_zero() {
return CalculationResult::Ok(Decimal::zero());
}
// We are only resulting in a negative if we have mismatched signs
let negative = d1.is_sign_negative() ^ d2.is_sign_negative();
// We get the scale of the result by adding the operands. This may be too big, however
// we'll correct later
let mut final_scale = d1.scale() + d2.scale();
// First of all, if ONLY the lo parts of both numbers is filled
// then we can simply do a standard 64 bit calculation. It's a minor
// optimization however prevents the need for long form multiplication
let my = d1.mantissa_array3();
let ot = d2.mantissa_array3();
if my[1] == 0 && my[2] == 0 && ot[1] == 0 && ot[2] == 0 {
// Simply multiplication
let mut u64_result = u64_to_array(u64::from(my[0]) * u64::from(ot[0]));
// If we're above max precision then this is a very small number
if final_scale > MAX_PRECISION_U32 {
final_scale -= MAX_PRECISION_U32;
// If the number is above 19 then this will equate to zero.
// This is because the max value in 64 bits is 1.84E19
if final_scale > 19 {
return CalculationResult::Ok(Decimal::zero());
}
let mut rem_lo = 0;
let mut power;
if final_scale > 9 {
// Since 10^10 doesn't fit into u32, we divide by 10^10/4
// and multiply the next divisor by 4.
rem_lo = div_by_u32(&mut u64_result, 2_500_000_000);
power = POWERS_10[final_scale as usize - 10] << 2;
} else {
power = POWERS_10[final_scale as usize];
}
// Divide fits in 32 bits
let rem_hi = div_by_u32(&mut u64_result, power);
// Round the result. Since the divisor is a power of 10
// we check to see if the remainder is >= 1/2 divisor
power >>= 1;
if rem_hi >= power && (rem_hi > power || (rem_lo | (u64_result[0] & 0x1)) != 0) {
u64_result[0] += 1;
}
final_scale = MAX_PRECISION_U32;
}
return CalculationResult::Ok(Decimal::from_parts(
u64_result[0],
u64_result[1],
0,
negative,
final_scale,
));
}
// We're using some of the high bits, so we essentially perform
// long form multiplication. We compute the 9 partial products
// into a 192 bit result array.
//
// [my-h][my-m][my-l]
// x [ot-h][ot-m][ot-l]
// --------------------------------------
// 1. [r-hi][r-lo] my-l * ot-l [0, 0]
// 2. [r-hi][r-lo] my-l * ot-m [0, 1]
// 3. [r-hi][r-lo] my-m * ot-l [1, 0]
// 4. [r-hi][r-lo] my-m * ot-m [1, 1]
// 5. [r-hi][r-lo] my-l * ot-h [0, 2]
// 6. [r-hi][r-lo] my-h * ot-l [2, 0]
// 7. [r-hi][r-lo] my-m * ot-h [1, 2]
// 8. [r-hi][r-lo] my-h * ot-m [2, 1]
// 9.[r-hi][r-lo] my-h * ot-h [2, 2]
let mut product = [0u32, 0u32, 0u32, 0u32, 0u32, 0u32];
// We can perform a minor short circuit here. If the
// high portions are both 0 then we can skip portions 5-9
let to = if my[2] == 0 && ot[2] == 0 { 2 } else { 3 };
for (my_index, my_item) in my.iter().enumerate().take(to) {
for (ot_index, ot_item) in ot.iter().enumerate().take(to) {
let (mut rlo, mut rhi) = mul_part(*my_item, *ot_item, 0);
// Get the index for the lo portion of the product
for prod in product.iter_mut().skip(my_index + ot_index) {
let (res, overflow) = add_part(rlo, *prod);
*prod = res;
// If we have something in rhi from before then promote that
if rhi > 0 {
// If we overflowed in the last add, add that with rhi
if overflow > 0 {
let (nlo, nhi) = add_part(rhi, overflow);
rlo = nlo;
rhi = nhi;
} else {
rlo = rhi;
rhi = 0;
}
} else if overflow > 0 {
rlo = overflow;
rhi = 0;
} else {
break;
}
// If nothing to do next round then break out
if rlo == 0 {
break;
}
}
}
}
// If our result has used up the high portion of the product
// then we either have an overflow or an underflow situation
// Overflow will occur if we can't scale it back, whereas underflow
// with kick in rounding
let mut remainder = 0;
while final_scale > 0 && (product[3] != 0 || product[4] != 0 || product[5] != 0) {
remainder = div_by_u32(&mut product, 10u32);
final_scale -= 1;
}
// Round up the carry if we need to
if remainder >= 5 {
for part in product.iter_mut() {
if remainder == 0 {
break;
}
let digit: u64 = u64::from(*part) + 1;
remainder = if digit > 0xFFFF_FFFF { 1 } else { 0 };
*part = (digit & 0xFFFF_FFFF) as u32;
}
}
// If we're still above max precision then we'll try again to
// reduce precision - we may be dealing with a limit of "0"
if final_scale > MAX_PRECISION_U32 {
// We're in an underflow situation
// The easiest way to remove precision is to divide off the result
while final_scale > MAX_PRECISION_U32 && !is_all_zero(&product) {
div_by_u32(&mut product, 10);
final_scale -= 1;
}
// If we're still at limit then we can't represent any
// significant decimal digits and will return an integer only
// Can also be invoked while representing 0.
if final_scale > MAX_PRECISION_U32 {
final_scale = 0;
}
} else if !(product[3] == 0 && product[4] == 0 && product[5] == 0) {
// We're in an overflow situation - we're within our precision bounds
// but still have bits in overflow
return CalculationResult::Overflow;
}
CalculationResult::Ok(Decimal::from_parts(
product[0],
product[1],
product[2],
negative,
final_scale,
))
}
pub(crate) fn rem_impl(d1: &Decimal, d2: &Decimal) -> CalculationResult {
if d2.is_zero() {
return CalculationResult::DivByZero;
}
if d1.is_zero() {
return CalculationResult::Ok(Decimal::zero());
}
// Rescale so comparable
let initial_scale = d1.scale();
let mut quotient = d1.mantissa_array3();
let mut quotient_scale = initial_scale;
let mut divisor = d2.mantissa_array3();
let mut divisor_scale = d2.scale();
rescale_to_maximum_scale(&mut quotient, &mut quotient_scale, &mut divisor, &mut divisor_scale);
// Working is the remainder + the quotient
// We use an aligned array since we'll be using it a lot.
let mut working_quotient = [quotient[0], quotient[1], quotient[2], 0u32];
let mut working_remainder = [0u32, 0u32, 0u32, 0u32];
div_internal(&mut working_quotient, &mut working_remainder, &divisor);
// Round if necessary. This is for semantic correctness, but could feasibly be removed for
// performance improvements.
if quotient_scale > initial_scale {
let mut working = [
working_remainder[0],
working_remainder[1],
working_remainder[2],
working_remainder[3],
];
while quotient_scale > initial_scale {
if div_by_u32(&mut working, 10) > 0 {
break;
}
quotient_scale -= 1;
working_remainder.copy_from_slice(&working);
}
}
CalculationResult::Ok(Decimal::from_parts(
working_remainder[0],
working_remainder[1],
working_remainder[2],
d1.is_sign_negative(),
quotient_scale,
))
}
pub(crate) fn cmp_impl(d1: &Decimal, d2: &Decimal) -> Ordering {
// Quick exit if major differences
if d1.is_zero() && d2.is_zero() {
return Ordering::Equal;
}
let self_negative = d1.is_sign_negative();
let other_negative = d2.is_sign_negative();
if self_negative && !other_negative {
return Ordering::Less;
} else if !self_negative && other_negative {
return Ordering::Greater;
}
// If we have 1.23 and 1.2345 then we have
// 123 scale 2 and 12345 scale 4
// We need to convert the first to
// 12300 scale 4 so we can compare equally
let left: &Decimal;
let right: &Decimal;
if self_negative && other_negative {
// Both are negative, so reverse cmp
left = d2;
right = d1;
} else {
left = d1;
right = d2;
}
let mut left_scale = left.scale();
let mut right_scale = right.scale();
let mut left_raw = left.mantissa_array3();
let mut right_raw = right.mantissa_array3();
if left_scale == right_scale {
// Fast path for same scale
if left_raw[2] != right_raw[2] {
return left_raw[2].cmp(&right_raw[2]);
}
if left_raw[1] != right_raw[1] {
return left_raw[1].cmp(&right_raw[1]);
}
return left_raw[0].cmp(&right_raw[0]);
}
// Rescale and compare
rescale_to_maximum_scale(&mut left_raw, &mut left_scale, &mut right_raw, &mut right_scale);
cmp_internal(&left_raw, &right_raw)
}
#[inline]
fn add_part(left: u32, right: u32) -> (u32, u32) {
let added = u64::from(left) + u64::from(right);
((added & U32_MASK) as u32, (added >> 32 & U32_MASK) as u32)
}
#[inline(always)]
fn sub_by_internal3(value: &mut [u32; 3], by: &[u32; 3]) {
let mut overflow = 0;
let vl = value.len();
for i in 0..vl {
let part = (0x1_0000_0000u64 + u64::from(value[i])) - (u64::from(by[i]) + overflow);
value[i] = part as u32;
overflow = 1 - (part >> 32);
}
}
fn div_internal(quotient: &mut [u32; 4], remainder: &mut [u32; 4], divisor: &[u32; 3]) {
// There are a couple of ways to do division on binary numbers:
// 1. Using long division
// 2. Using the complement method
// The complement method basically keeps trying to subtract the
// divisor until it can't anymore and placing the rest in remainder.
let mut complement = [
divisor[0] ^ 0xFFFF_FFFF,
divisor[1] ^ 0xFFFF_FFFF,
divisor[2] ^ 0xFFFF_FFFF,
0xFFFF_FFFF,
];
// Add one onto the complement
add_one_internal4(&mut complement);
// Make sure the remainder is 0
remainder.iter_mut().for_each(|x| *x = 0);
// If we have nothing in our hi+ block then shift over till we do
let mut blocks_to_process = 0;
while blocks_to_process < 4 && quotient[3] == 0 {
// memcpy would be useful here
quotient[3] = quotient[2];
quotient[2] = quotient[1];
quotient[1] = quotient[0];
quotient[0] = 0;
// Increment the counter
blocks_to_process += 1;
}
// Let's try and do the addition...
let mut block = blocks_to_process << 5;
let mut working = [0u32, 0u32, 0u32, 0u32];
while block < 128 {
// << 1 for quotient AND remainder. Moving the carry from the quotient to the bottom of the
// remainder.
let carry = shl1_internal(quotient, 0);
shl1_internal(remainder, carry);
// Copy the remainder of working into sub
working.copy_from_slice(remainder);
// Add the remainder with the complement
add_by_internal(&mut working, &complement);
// Check for the significant bit - move over to the quotient
// as necessary
if (working[3] & 0x8000_0000) == 0 {
remainder.copy_from_slice(&working);
quotient[0] |= 1;
}
// Increment our pointer
block += 1;
}
}
#[inline]
fn copy_array_diff_lengths(into: &mut [u32], from: &[u32]) {
for i in 0..into.len() {
if i >= from.len() {
break;
}
into[i] = from[i];
}
}
#[inline]
fn add_one_internal4(value: &mut [u32; 4]) -> u32 {
let mut carry: u64 = 1; // Start with one, since adding one
let mut sum: u64;
for i in value.iter_mut() {
sum = (*i as u64) + carry;
*i = (sum & U32_MASK) as u32;
carry = sum >> 32;
}
carry as u32
}
#[inline]
fn add_by_internal3(value: &mut [u32; 3], by: &[u32; 3]) -> u32 {
let mut carry: u32 = 0;
let bl = by.len();
for i in 0..bl {
let res1 = value[i].overflowing_add(by[i]);
let res2 = res1.0.overflowing_add(carry);
value[i] = res2.0;
carry = (res1.1 | res2.1) as u32;
}
carry
}
#[inline]
const fn u64_to_array(value: u64) -> [u32; 2] {
[(value & U32_MASK) as u32, (value >> 32 & U32_MASK) as u32]
}
fn add_with_scale_internal(
quotient: &mut [u32; 3],
quotient_scale: &mut i32,
working_quotient: &mut [u32; 4],
working_scale: &mut i32,
) -> bool {
// Add quotient and the working (i.e. quotient = quotient + working)
if is_all_zero(quotient) {
// Quotient is zero so we can just copy the working quotient in directly
// First, make sure they are both 96 bit.
while working_quotient[3] != 0 {
div_by_u32(working_quotient, 10);
*working_scale -= 1;
}
copy_array_diff_lengths(quotient, working_quotient);
*quotient_scale = *working_scale;
return false;
}
if is_all_zero(working_quotient) {
return false;
}
// We have ensured that our working is not zero so we should do the addition
// If our two quotients are different then
// try to scale down the one with the bigger scale
let mut temp3 = [0u32, 0u32, 0u32];
let mut temp4 = [0u32, 0u32, 0u32, 0u32];
if *quotient_scale != *working_scale {
// TODO: Remove necessity for temp (without performance impact)
fn div_by_10<const N: usize>(target: &mut [u32], temp: &mut [u32; N], scale: &mut i32, target_scale: i32) {
// Copy to the temp array
temp.copy_from_slice(target);
// divide by 10 until target scale is reached
while *scale > target_scale {
let remainder = div_by_u32(temp, 10);
if remainder == 0 {
*scale -= 1;
target.copy_from_slice(temp);
} else {
break;
}
}
}
if *quotient_scale < *working_scale {
div_by_10(working_quotient, &mut temp4, working_scale, *quotient_scale);
} else {
div_by_10(quotient, &mut temp3, quotient_scale, *working_scale);
}
}
// If our two quotients are still different then
// try to scale up the smaller scale
if *quotient_scale != *working_scale {
// TODO: Remove necessity for temp (without performance impact)
fn mul_by_10(target: &mut [u32], temp: &mut [u32], scale: &mut i32, target_scale: i32) {
temp.copy_from_slice(target);
let mut overflow = 0;
// Multiply by 10 until target scale reached or overflow
while *scale < target_scale && overflow == 0 {
overflow = mul_by_u32(temp, 10);
if overflow == 0 {
// Still no overflow
*scale += 1;
target.copy_from_slice(temp);
}
}
}
if *quotient_scale > *working_scale {
mul_by_10(working_quotient, &mut temp4, working_scale, *quotient_scale);
} else {
mul_by_10(quotient, &mut temp3, quotient_scale, *working_scale);
}
}
// If our two quotients are still different then
// try to scale down the one with the bigger scale
// (ultimately losing significant digits)
if *quotient_scale != *working_scale {
// TODO: Remove necessity for temp (without performance impact)
fn div_by_10_lossy<const N: usize>(
target: &mut [u32],
temp: &mut [u32; N],
scale: &mut i32,
target_scale: i32,
) {
temp.copy_from_slice(target);
// divide by 10 until target scale is reached
while *scale > target_scale {
div_by_u32(temp, 10);
*scale -= 1;
target.copy_from_slice(temp);
}
}
if *quotient_scale < *working_scale {
div_by_10_lossy(working_quotient, &mut temp4, working_scale, *quotient_scale);
} else {
div_by_10_lossy(quotient, &mut temp3, quotient_scale, *working_scale);
}
}
// If quotient or working are zero we have an underflow condition
if is_all_zero(quotient) || is_all_zero(working_quotient) {
// Underflow
return true;
} else {
// Both numbers have the same scale and can be added.
// We just need to know whether we can fit them in
let mut underflow = false;
let mut temp = [0u32, 0u32, 0u32];
while !underflow {
temp.copy_from_slice(quotient);
// Add the working quotient
let overflow = add_by_internal(&mut temp, working_quotient);
if overflow == 0 {
// addition was successful
quotient.copy_from_slice(&temp);
break;
} else {
// addition overflowed - remove significant digits and try again
div_by_u32(quotient, 10);
*quotient_scale -= 1;
div_by_u32(working_quotient, 10);
*working_scale -= 1;
// Check for underflow
underflow = is_all_zero(quotient) || is_all_zero(working_quotient);
}
}
if underflow {
return true;
}
}
false
}
/// Rescales the given decimals to equivalent scales.
/// It will firstly try to scale both the left and the right side to
/// the maximum scale of left/right. If it is unable to do that it
/// will try to reduce the accuracy of the other argument.
/// e.g. with 1.23 and 2.345 it'll rescale the first arg to 1.230
#[inline(always)]
fn rescale_to_maximum_scale(left: &mut [u32; 3], left_scale: &mut u32, right: &mut [u32; 3], right_scale: &mut u32) {
if left_scale == right_scale {
// Nothing to do
return;
}
if is_all_zero(left) {
*left_scale = *right_scale;
return;
} else if is_all_zero(right) {
*right_scale = *left_scale;
return;
}
if left_scale > right_scale {
rescale_internal(right, right_scale, *left_scale);
if right_scale != left_scale {
rescale_internal(left, left_scale, *right_scale);
}
} else {
rescale_internal(left, left_scale, *right_scale);
if right_scale != left_scale {
rescale_internal(right, right_scale, *left_scale);
}
}
}
#[cfg(test)]
mod test {
// Tests on private methods.
//
// All public tests should go under `tests/`.
use super::*;
use crate::prelude::*;
#[test]
fn it_can_rescale_to_maximum_scale() {
fn extract(value: &str) -> ([u32; 3], u32) {
let v = Decimal::from_str(value).unwrap();
(v.mantissa_array3(), v.scale())
}
let tests = &[
("1", "1", "1", "1"),
("1", "1.0", "1.0", "1.0"),
("1", "1.00000", "1.00000", "1.00000"),
("1", "1.0000000000", "1.0000000000", "1.0000000000"),
(
"1",
"1.00000000000000000000",
"1.00000000000000000000",
"1.00000000000000000000",
),
("1.1", "1.1", "1.1", "1.1"),
("1.1", "1.10000", "1.10000", "1.10000"),
("1.1", "1.1000000000", "1.1000000000", "1.1000000000"),
(
"1.1",
"1.10000000000000000000",
"1.10000000000000000000",
"1.10000000000000000000",
),
(
"0.6386554621848739495798319328",
"11.815126050420168067226890757",
"0.638655462184873949579831933",
"11.815126050420168067226890757",
),
(
"0.0872727272727272727272727272", // Scale 28
"843.65000000", // Scale 8
"0.0872727272727272727272727", // 25
"843.6500000000000000000000000", // 25
),
];
for &(left_raw, right_raw, expected_left, expected_right) in tests {
// Left = the value to rescale
// Right = the new scale we're scaling to
// Expected = the expected left value after rescale
let (expected_left, expected_lscale) = extract(expected_left);
let (expected_right, expected_rscale) = extract(expected_right);
let (mut left, mut left_scale) = extract(left_raw);
let (mut right, mut right_scale) = extract(right_raw);
rescale_to_maximum_scale(&mut left, &mut left_scale, &mut right, &mut right_scale);
assert_eq!(left, expected_left);
assert_eq!(left_scale, expected_lscale);
assert_eq!(right, expected_right);
assert_eq!(right_scale, expected_rscale);
// Also test the transitive case
let (mut left, mut left_scale) = extract(left_raw);
let (mut right, mut right_scale) = extract(right_raw);
rescale_to_maximum_scale(&mut right, &mut right_scale, &mut left, &mut left_scale);
assert_eq!(left, expected_left);
assert_eq!(left_scale, expected_lscale);
assert_eq!(right, expected_right);
assert_eq!(right_scale, expected_rscale);
}
}
}