Functional Weave
Code in Rust

finance.margin@1.0.0

impl/rust.rs

3,211 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_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]),
    ))
}