Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::math_integer_sqrt::integer_sqrt; ← from math.integer-sqrt ^1.0.0 · built alongside by fune
use super::money_amount::{assert_same_currency, money_from_value, Money}; ← from money.amount ^1.0.0 · built alongside by fune
const MAX_SAFE: i64 = (1i64 << 53) - 1;
/// Economic batch quantity, sqrt(2DSP / (H(P - D))), 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.
///
/// # Panics
/// Panics on negative demand or set-up cost, a production rate not above
/// demand, a holding cost that is not positive, mixed currencies, an unknown
/// mode, or 8DSP beyond 2^63 - 1.
pub fn economic_batch_quantity(
annual_demand: i64,
annual_production_rate: i64,
setup_cost: &Money,
holding_cost: &Money,
mode: &str,
) -> i64 {
if !(0..=MAX_SAFE).contains(&annual_demand) {
panic!("annualDemand must be a whole number of units, not negative, received {}", annual_demand);
}
if annual_production_rate <= annual_demand || annual_production_rate > MAX_SAFE {
panic!(
"annualProductionRate must be a whole number greater than annualDemand, received {}",
annual_production_rate
);
}
assert_same_currency(setup_cost, holding_cost);
if setup_cost.minor < 0 {
panic!("setupCost must not be negative, received {}", setup_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 p = annual_production_rate as i128;
let s = setup_cost.minor as i128;
let eight = (8 * d)
.checked_mul(s)
.and_then(|x| x.checked_mul(p))
.filter(|x| *x <= i64::MAX as i128)
.unwrap_or_else(|| {
panic!("annualDemand, setupCost and annualProductionRate are too large: 8 x D x S x P must stay within 2^63 - 1")
});
let two = eight / 4;
let m = holding_cost.minor as i128 * (p - d);
let n = integer_sqrt((two / m) as i64) as i128;
let odd = 2 * n + 1;
let result = match mode {
"down" => n,
"up" => {
if n * n * m == two {
n
} else {
n + 1
}
}
"half-up" => {
if odd * odd * m <= eight {
n + 1
} else {
n
}
}
"half-even" => {
let half = odd * odd * m;
if half < eight || (half == eight && 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);
}
if let Value::Float(f) = &args[1] {
panic!("annualProductionRate must be a whole number greater than annualDemand, received {}", f);
}
Value::Int(economic_batch_quantity(
args[0].as_i64(),
args[1].as_i64(),
&money_from_value(&args[2]),
&money_from_value(&args[3]),
args[4].as_str(),
))
}