How to extract integer part and decimal part from f64

Implement From trait for my struct: convert to my type from f64 value(extract integer part a and decimal part bc from f64 value that looks like a.bc, b and c are integers between 0 and 9)

Implement

#[derive(Debug, Clone, Copy, PartialEq)]
pub struct MoneyAmount {
    int_part: u16,
    deci_part: u8,
}

impl From<f64> for MoneyAmount {
    fn from(value: f64) -> Self {
        let int_part = value.trunc();
        let deci_part = (value - int_part) * 100.0;

        Self { 
            int_part: int_part as u16,
            deci_part: deci_part as u8,
        }
    }
}

Test Cases

#[cfg(test)]
mod tests{
    use super::*;

    // --- snip ---
    #[test]
    fn create_from_f64() {
        let money = MoneyAmount::from(0.00);
        assert_eq!(money, MoneyAmount::new(0, 0).unwrap());

        let money = MoneyAmount::from(1.00);
        assert_eq!(money, MoneyAmount::new(1, 0).unwrap());
		
        // failure here ↓
        let money = MoneyAmount::from(2.88);
        assert_eq!(money, MoneyAmount::new(2, 88).unwrap());

        let money = MoneyAmount::from(12.04);
        assert_eq!(money, MoneyAmount::new(12, 4).unwrap());

        let money = MoneyAmount::from(200.00);
        assert_eq!(money, MoneyAmount::new(200, 0).unwrap());
    }

}

Failure Stdout

assertion `left == right` failed
  left: MoneyAmount { int_part: 2, deci_part: 87 }
  right: MoneyAmount { int_part: 2, deci_part: 88 }

Debug

When you type 2.88, it's not actually 2.88, its roughly 2.87999999999999989342 (see float.exposed/0x40070a3d70a3d70a).
That's why trunc returns the "wrong" result.

The solution is to never use floating point types in this scenario, but a decimal type found in the crates rust_decimal - Rust or bigdecimal - Rust

5 Likes

Thanks a lot, it's what I want :sunny:

It's an interesting exercise to implement a money type yourself; it teaches you a lot about the pitfalls around doing computation on exact precision values, of which there are many

2 Likes