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_round_div::round_div; ← from math.round-div ^1.0.0 · built alongside by fune
use super::money_add::subtract_money; ← from money.add ^1.0.0 · built alongside by fune
use super::money_amount::{money_from_value, money_to_value, Money}; ← from money.amount ^1.0.0 · built alongside by fune
/// Profit on a sale, expressed both ways.
///
/// Margin divides by the price, markup divides by the cost, and the gap between
/// them is wide enough to price a whole range wrongly: 10.00 to 15.00 is a 50%
/// markup and a 33.33% margin. Returning only one of them invites the caller to
/// use it as the other, so both are returned and neither is called "the rate".
///
/// Both ratios are in basis points and come out of integer division, so no
/// float ever touches the money.
///
/// # Panics
/// Panics if cost and price are in different currencies.
pub fn margin(cost: &Money, price: &Money) -> MarginBreakdown {
// subtract_money also rejects a GBP cost against a EUR price, which is the
// only way this function can be called with genuinely unanswerable inputs.
let profit = subtract_money(price, cost);
// A zero denominator is not an error here: a giveaway (price 0) and a free
// sample (cost 0) are real trade. The ratio is undefined, not wrong, and
// the rest of the breakdown is still correct.
let margin_basis_points = if price.minor == 0 {
None
} else {
Some(round_div(profit.minor * 10000, price.minor, "half-up"))
};
let markup_basis_points = if cost.minor == 0 {
None
} else {
Some(round_div(profit.minor * 10000, cost.minor, "half-up"))
};
MarginBreakdown {
cost: cost.clone(),
price: price.clone(),
profit,
margin_basis_points,
markup_basis_points,
}
}
/// Convert a margin to the markup that produces it. Undefined at 100% margin.
///
/// # Panics
/// Panics at or above a 100% margin, where the markup is infinite.
pub fn margin_to_markup_basis_points(margin_basis_points: i64) -> i64 {
if margin_basis_points >= 10000 {
panic!(
"a margin of {} basis points has no finite markup",
margin_basis_points
);
}
round_div(margin_basis_points * 10000, 10000 - margin_basis_points, "half-up")
}
/// Convert a markup to the margin it produces.
pub fn markup_to_margin_basis_points(markup_basis_points: i64) -> i64 {
round_div(markup_basis_points * 10000, 10000 + markup_basis_points, "half-up")
}
fn basis_points_to_value(basis_points: Option<i64>) -> Value {
match basis_points {
Some(bp) => Value::Int(bp),
None => Value::Null,
}
}
pub fn margin_breakdown_to_value(breakdown: &MarginBreakdown) -> Value {
Value::obj(vec![
("cost", money_to_value(&breakdown.cost)),
("price", money_to_value(&breakdown.price)),
("profit", money_to_value(&breakdown.profit)),
(
"marginBasisPoints",
basis_points_to_value(breakdown.margin_basis_points),
),
(
"markupBasisPoints",
basis_points_to_value(breakdown.markup_basis_points),
),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
margin_breakdown_to_value(&margin(
&money_from_value(&args[0]),
&money_from_value(&args[1]),
))
}