use super::funejson::Value; use super::math_round_div::round_div; use super::money_amount::{money, money_from_value, money_to_value, Money}; /// Snap a price to the points k x step - ending. /// /// Shifting by `ending` turns every point into a multiple of `step`, so the /// whole policy is one integer division with the right rounding mode. /// /// # Panics /// Panics on a step below 1, an ending outside 0..step, a negative price, /// `down` below the first point, or an unknown direction. pub fn round_price(price: &Money, step: i64, ending: i64, direction: &str) -> Money { if step < 1 { panic!("step must be 1 or more, received {}", step); } if ending < 0 || ending >= step { panic!("ending must be from 0 to step - 1, received {}", ending); } if price.minor < 0 { panic!("price must not be negative, received {}", price.minor); } let shifted = price.minor + ending; let point = match direction { "up" => round_div(shifted, step, "up") * step - ending, "down" => { let point = round_div(shifted, step, "down") * step - ending; if point < 0 { panic!("no price point at or below {}", price.minor); } point } "nearest" => { let point = round_div(shifted, step, "half-up") * step - ending; // Below the first point the only candidate is above. if point < 0 { step - ending } else { point } } other => panic!("unknown direction \"{}\"", other), }; money(point, &price.currency) } pub fn fune_vector(args: &[Value]) -> Value { money_to_value(&round_price( &money_from_value(&args[0]), args[1].as_i64(), args[2].as_i64(), args[3].as_str(), )) }