Functional Weave
Code in Python

inventory.eoq@1.0.0

impl/rust.rs

2,635 bytes · the Rust implementation · view raw

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: 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(),
    ))
}