from .math_integer_sqrt import integer_sqrt from .math_round_div import RoundingMode from .money_amount import Money, assert_same_currency MAX_SAFE = 2**53 - 1 def economic_order_quantity(annual_demand: int, order_cost: Money, holding_cost: Money, mode: RoundingMode) -> int: """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. """ if isinstance(annual_demand, bool) or not isinstance(annual_demand, int) or annual_demand < 0: raise ValueError( "annualDemand must be a whole number of units, not negative, received %r" % (annual_demand,) ) assert_same_currency(order_cost, holding_cost) if order_cost.minor < 0: raise ValueError("orderCost must not be negative, received %d" % order_cost.minor) if holding_cost.minor <= 0: raise ValueError("holdingCost must be greater than zero, received %d" % holding_cost.minor) eight_ds = 8 * annual_demand * order_cost.minor if eight_ds > MAX_SAFE: raise ValueError( "annualDemand and orderCost are too large: 8 x demand x order cost must stay within 2^53 - 1" ) two_ds = 2 * annual_demand * order_cost.minor h = holding_cost.minor n = integer_sqrt(two_ds // h) odd = 2 * n + 1 if mode == "down": return n if mode == "up": return n if n * n * h == two_ds else n + 1 if mode == "half-up": return n + 1 if odd * odd * h <= eight_ds else n if mode == "half-even": half = odd * odd * h if half < eight_ds: return n + 1 if half > eight_ds: return n return n if n % 2 == 0 else n + 1 raise ValueError('unknown rounding mode "%s"' % (mode,))