use super::funejson::Value; use super::math_round_div::round_div; use super::money_amount::{money, money_from_value, money_to_value, Money}; const MAX_SAFE: i128 = 9_007_199_254_740_991; /// The selling price that hits a target margin or markup on `cost`. /// /// Margin divides by the price and markup by the cost, so the same 5000 basis /// points means 200.00 or 150.00 on a cost of 100.00; the basis is explicit. /// /// # Panics /// Panics on a negative cost, a margin of 100% or more, a markup below -100%, /// an unknown basis, or a calculation beyond 2^53 - 1. pub fn markup_price(cost: &Money, basis_points: i64, basis: &str, mode: &str) -> Money { if cost.minor < 0 { panic!("cost must not be negative, received {}", cost.minor); } let (numerator, denominator): (i128, i128) = match basis { "markup" => { if basis_points < -10000 { panic!("a markup of {} basis points gives a negative price", basis_points); } (cost.minor as i128 * (10000 + basis_points as i128), 10000) } "margin" => { if basis_points >= 10000 { panic!("a margin of {} basis points has no finite price", basis_points); } (cost.minor as i128 * 10000, 10000 - basis_points as i128) } other => panic!("unknown pricing basis \"{}\"", other), }; // i64 could go further, but TypeScript cannot, and all three must agree. if numerator > MAX_SAFE { panic!("the price calculation exceeds 2^53 - 1"); } money( round_div(numerator as i64, denominator as i64, mode), &cost.currency, ) } pub fn fune_vector(args: &[Value]) -> Value { money_to_value(&markup_price( &money_from_value(&args[0]), args[1].as_i64(), args[2].as_str(), args[3].as_str(), )) }