Table of Contents

Work with money, amounts, and percentages

Financial values need more than a decimal. They need a clear currency policy, controlled rounding, and failures that remain visible when a calculation is invalid.

Choose the value that carries the right meaning

Type Use it when
MonetaryAmount The bounded context has one external currency policy, so the value's identity is only a non-negative amount.
Money Currency travels with the amount and participates in equality and arithmetic.
Percentage The domain uses a value from 0 through 100, or receives a fraction from 0 through 1.
CurrencyCode You need a standalone three-letter currency-shaped code.

Install the package:

dotnet add package Trellis.Primitives

Keep calculations on the railway

This complete console example validates a subtotal and tax fraction, then calculates the total without introducing an exception path:

using Trellis;
using Trellis.Primitives;

var result = Checkout.TotalWithTax(
    subtotal: 120m,
    currency: "USD",
    taxFraction: 0.0825m);

if (!result.TryGetValue(out var total, out var error))
{
    Console.Error.WriteLine(error);
    return;
}

Console.WriteLine(total);

public static class Checkout
{
    public static Result<Money> TotalWithTax(
        decimal subtotal,
        string currency,
        decimal taxFraction) =>
        Money.TryCreate(
                subtotal,
                currency,
                nameof(subtotal),
                nameof(currency))
            .Bind(money =>
                Percentage.FromFraction(taxFraction, nameof(taxFraction))
                    .Bind(rate =>
                        money.Multiply(rate.AsFraction())
                            .Bind(money.Add)));
}

Money.Multiply and Money.Add return Result<Money>. A negative subtotal, invalid fraction, arithmetic overflow, or currency mismatch remains a typed failure for the caller to handle.

Currency is part of the rule

Money.Add, Subtract, and Sum require matching currencies. CurrencyCode normalizes case and validates a three-letter ASCII shape, but it does not enforce the active ISO 4217 list or a payment provider's supported set. Add that policy at your application boundary.

Money rounds according to the currency's minor units. Use MonetaryAmount only when a single-currency policy is already guaranteed outside the value itself.

Split and aggregate deliberately

Money.Allocate divides an amount by positive ratios while distributing the minor-unit remainder. Money.Sum rejects an empty sequence and mixed currencies; its fallback overload lets the caller provide a meaningful currency for an empty sequence.

For percentage input, use Percentage.TryCreate when the input is already in 0..100, and Percentage.FromFraction for 0..1. AsFraction() converts back before multiplication.

Use the generated API pages for the complete arithmetic and overload reference: