use super::funejson::Value; use super::math_integer_sqrt::integer_sqrt; use super::money_amount::{assert_same_currency, money_from_value, Money}; const MAX_SAFE: i128 = (1i128 << 53) - 1; /// Economic order quantity, sqrt(2DS / H), in whole units. /// /// The root is never taken in floating point: the floor is an exact integer /// square root and the rounding decision compares whole numbers, so an exact /// half (sqrt(6.25) = 2.5) rounds by `mode` and not by the language. /// /// # Panics /// Panics on negative demand or order cost, a holding cost that is not /// positive, mixed currencies, an unknown mode, or 8DS beyond 2^53 - 1. pub fn economic_order_quantity(annual_demand: i64, order_cost: &Money, holding_cost: &Money, mode: &str) -> i64 { if annual_demand < 0 { panic!( "annualDemand must be a whole number of units, not negative, received {}", annual_demand ); } assert_same_currency(order_cost, holding_cost); if order_cost.minor < 0 { panic!("orderCost must not be negative, received {}", order_cost.minor); } if holding_cost.minor <= 0 { panic!("holdingCost must be greater than zero, received {}", holding_cost.minor); } let d = annual_demand as i128; let s = order_cost.minor as i128; let h = holding_cost.minor as i128; let eight_ds = 8 * d * s; if eight_ds > MAX_SAFE { panic!("annualDemand and orderCost are too large: 8 x demand x order cost must stay within 2^53 - 1"); } let two_ds = 2 * d * s; let n = integer_sqrt((two_ds / h) as i64) as i128; let odd = 2 * n + 1; let result = match mode { "down" => n, "up" => { if n * n * h == two_ds { n } else { n + 1 } } "half-up" => { if odd * odd * h <= eight_ds { n + 1 } else { n } } "half-even" => { let half = odd * odd * h; if half < eight_ds || (half == eight_ds && n % 2 == 1) { n + 1 } else { n } } other => panic!("unknown rounding mode \"{}\"", other), }; result as i64 } pub fn fune_vector(args: &[Value]) -> Value { if let Value::Float(f) = &args[0] { panic!("annualDemand must be a whole number of units, not negative, received {}", f); } Value::Int(economic_order_quantity( args[0].as_i64(), &money_from_value(&args[1]), &money_from_value(&args[2]), args[3].as_str(), )) }