ckb icon indicating copy to clipboard operation
ckb copied to clipboard

A better implementation for `RationalU256`.

Open chaoticlonghair opened this issue 3 years ago • 3 comments

Issue

Panicked at 'U256: attempt to multiply with overflow' for some values.

https://github.com/nervosnetwork/ckb/blob/649389d90a142cf39cdbc2019b8fbde188383ca5/util/rational/src/lib.rs#L128-L140

Reproduce

Multiply two RationalU256 numbers, when both two numerators (or denominators) are big but their greatest common divisor is small.

For example:

use ckb_types::{core::RationalU256, u256, U256};
fn main() {
    let x = u256!("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
    let x_rational = RationalU256::from_u256(x.clone());
    let numerator = 33u32;
    let denominator = 100u32;
    let y_rational = RationalU256::new(U256::from(numerator), U256::from(denominator));
    let z_rational = x_rational * y_rational; // panicked in this line!
    let z = z_rational.into_u256();
    println!("{:#x} = {:#x} * {}/{}", z, x, numerator, denominator);
}

Ref: nervosnetwork/ckb-light-client#44

chaoticlonghair avatar Aug 11 '22 02:08 chaoticlonghair

use ckb_types::{core::RationalU256, u256, U256};
fn main() {
    let x = u256!("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
    let x_rational = RationalU256::from_u256(x.clone());
    let numerator = 33u32;
    let denominator = 100u32;
    let y_rational = RationalU256::new(U256::from(numerator), U256::from(denominator));
    let z_rational = x_rational * y_rational; // panicked in this line!
    let z = z_rational.into_u256();
    println!("{:#x} = {:#x} * {}/{}", z, x, numerator, denominator);
}

I think It's expected that the very large x_rational multiply with y_rational cause overflow and panic. If the multiplication of two values cause overflow, then it warns developer to use larger type like U512 or U1024.

If we don't do that, the result may lose precision, like:

impl Mul<&RationalU256> for &RationalU256 {
    type Output = RationalU256;

    fn mul(self, rhs: &RationalU256) -> RationalU256 {
        let gcd_ad = self.numer.gcd(&rhs.denom);
        let gcd_bc = self.denom.gcd(&rhs.numer);

        let (numer1, _): (U512, bool) = (&self.numer / &gcd_ad).convert_into();
        let (denom1, _): (U512, bool) = (&rhs.numer / &gcd_bc).convert_into();
        let number: U512 = numer1 * denom1;

        let (numer2, _): (U512, bool) = (&self.denom / &gcd_bc).convert_into();
        let (denom2, _): (U512, bool) = (&rhs.denom / &gcd_ad).convert_into();
        let denom: U512 = numer2 * denom2;

        // put U512/U512 to U256/U256 may lose precision, this is not expected
        let result :RationalU256 =  RationalU512::new_raw(number, denom).reduce_to_U256();
        result
    }
}

eval-exec avatar Oct 12 '22 14:10 eval-exec