Weighted average over integers while maintaining precision

Consider the following code:

#[derive(Debug)]
struct Record {
    price: i64,
    weight: i64,
}

impl Record {
    pub fn new(p: i64, w: i64) -> Record {
        Record {
            price: p,
            weight: w,
        }
    }
}


pub fn main() {
    let dataset = vec![
        Record { price: 100, weight: 10 },
        Record { price: 200, weight: 15 },
        Record { price: 263, weight: 4 },
    ];
    
    let w_avg = dataset.iter().fold(Record::new(0, 0), |mut acc, row| {
        let total_w = acc.weight + row.weight;
        acc.price = (acc.price * acc.weight + row.price * row.weight) / total_w;
        acc.weight = total_w;
        acc
    });
    
    println!("{:?}", w_avg);
}

Actual output:

Record { price: 174, weight: 29 }

More precise (desired) output:

Record { price: 174.206896552, weight: 29 }

Now I understand this is because of type safety in Rust and absence of auto type promotion. One way is to declare Record.price as f64 and type cast the entries when instantiating the struct.

Is there any other way? May be some way to make Record.price generic over integers and floating points?

Playground
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c876bd8950e457b427bdce5de55c63f9

Have you tried doing just that? Try adding a generic parameter to the struct and use it instead of i64 within it.

And of course the usual caveat for anything that looks like money, never ever ever ever store money in floats, ever ever ever. An average of prices sure, but not the price itself.

Honestly I would not do a generic struct, I would have a different struct to return the average values. You are currently trying to store two different kinds and usages of data in the same object and they really do not belong together like that.

1 Like