Functional Weave
Code in Python

finance.depreciation-reducing-balance

Reducing-balance depreciation schedule at a fixed rate, writing off to the residual value in the final year.

1.0.0 · published 2026-10-03 by charlie · Anterra

Pinned by 17 tests, run in TypeScript, Python and Rust.

What it does

Each year's charge is a fixed rate of the asset's opening (written-down) value, so the charges fall year by year: 25% of 10,000.00 is 2,500.00, then 25% of 7,500.00 is 1,875.00, and so on. Also called declining-balance or diminishing-value depreciation.

A reducing balance never reaches the residual value by itself, so the **final year writes off whatever is left**: its charge is the opening value less the residual value, and the schedule closes on the residual value exactly. For 10,000.00 with a 1,000.00 residual at 25% over four years the charges are 2,500.00, 1,875.00, 1,406.25 and then 3,218.75 to write down to 1,000.00.

For example

  • reducing_balance_depreciation(£10,000.00, £1,000.00, 25%, 4) → ×4 25% over four years, the final year writing off down to the residual value
  • reducing_balance_depreciation(£999.99, £0.00, 30%, 3) → ×3 each year's charge is rounded half-up once, on that year's opening value
  • reducing_balance_depreciation(£0.10, £0.00, 25%, 2) → ×2 an exact half penny rounds up

The function

The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.

def reducing_balance_depreciation(cost: Money, residual_value: Money, rate_basis_points: int, years: int) -> List[DepreciationPeriod]
costMoneycapitalised cost of the asset
residual_valueMoneyexpected value at the end of its life; 0 if written down to nothing
rate_basis_pointsintthe yearly rate on the opening value, 2500 = 25%; 0 to 10000
yearsintuseful life in whole years, at least 1
returnsDepreciationPeriod[]

Your code names it in one line, in the file that uses it

from fune.finance.depreciation_reducing_balance import reducing_balance_depreciation  # finance.depreciation-reducing-balance@^1
impl/python.py · 46 lines · open · raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

from typing import List

from .finance_depreciation_straight_line_types import DepreciationPeriod
from .money_add import subtract_money  ← from money.add ^1.0.0 · built alongside by fune
from .money_amount import Money  ← from money.amount ^1.0.0 · built alongside by fune
from .money_apply_rate import apply_rate  ← from money.apply-rate ^1.0.0 · built alongside by fune


def reducing_balance_depreciation(
    cost: Money, residual_value: Money, rate_basis_points: int, years: int
) -> List[DepreciationPeriod]:
    """A reducing-balance depreciation schedule, year by year.

    Each charge is the rate applied to the opening value, rounded once. The
    last year writes off down to the residual value, and no year may go below it.
    """
    if isinstance(years, bool) or not isinstance(years, int) or years < 1:
        raise ValueError("years must be at least 1, received %r" % (years,))
    if (
        isinstance(rate_basis_points, bool)
        or not isinstance(rate_basis_points, int)
        or rate_basis_points < 0
        or rate_basis_points > 10000
    ):
        raise ValueError("rate must be 0 to 10000 basis points, received %r" % (rate_basis_points,))
    if cost.minor < 0:
        raise ValueError("cost must not be negative")
    if residual_value.minor < 0:
        raise ValueError("residual value must not be negative")
    # subtract_money also refuses a cost and residual value in different currencies.
    if subtract_money(cost, residual_value).minor < 0:
        raise ValueError("residual value must not exceed cost")

    schedule: List[DepreciationPeriod] = []
    opening_value = cost
    for year in range(1, years + 1):
        headroom = subtract_money(opening_value, residual_value)
        charge = headroom if year == years else apply_rate(opening_value, rate_basis_points, "half-up")
        if charge.minor > headroom.minor:
            charge = headroom
        closing_value = subtract_money(opening_value, charge)
        schedule.append(
            DepreciationPeriod(year=year, opening_value=opening_value, charge=charge, closing_value=closing_value)
        )
        opening_value = closing_value
    return schedule

Install

fune build

With that line in your source, in a Python project (language python in fune.project), fune build resolves it and its 4 dependencies, pins them in fune.lock, downloads only the Python package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. Or pin a range in fune.project and build in one step:

fune add finance.depreciation-reducing-balance
Download for Python finance.depreciation-reducing-balance-1.0.0-python.fune · 13,814 bytes sha256 dc87d616d0de0fa8c892f1fdbd7b0efcc13b269548ca17bbf57c2db52cdd045e

The manifest, vectors and README with only the Python implementation. Install it without the registry with fune add ./finance.depreciation-reducing-balance-1.0.0-python.fune, or fetch it from a terminal with fune pull finance.depreciation-reducing-balance@1.0.0:python.

The whole function, every language, is one file too: finance.depreciation-reducing-balance-1.0.0.fune, 18,429 bytes, sha256 69696d6a77b88def88901f5e5d0f3d6a16df86ebce536e0ce9e53d3dd4ceaa34. It installs into a project of any language.

Customise it in your app

The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.

before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.

# fune: before finance.depreciation-reducing-balance

after — your function gets the result and the arguments, and returns the final result.

# fune: after finance.depreciation-reducing-balance

replace — inside this capability’s code only, calls to a dependency go to your function, with the same signature. Other capabilities that use it are unaffected; write in * to replace it everywhere.

# fune: replace finance.depreciation-straight-line in finance.depreciation-reducing-balance
# fune: replace money.add in finance.depreciation-reducing-balance
# fune: replace money.amount in finance.depreciation-reducing-balance
# fune: replace money.apply-rate in finance.depreciation-reducing-balance

step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show finance.depreciation-reducing-balance --steps.

# fune: step finance.depreciation-reducing-balance after <n|label>

Tests

A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.

CaseArgumentsExpected
25% over four years, the final year writing off down to the residual value £10,000.00, £1,000.00, 25%, 4 → ×4
each year's charge is rounded half-up once, on that year's opening value £999.99, £0.00, 30%, 3 → ×3
an exact half penny rounds up £0.10, £0.00, 25%, 2 → ×2
a high rate reaching the residual value early is capped there, and later years charge nothing £1,000.00, £600.00, 50%, 3 → ×3
a 100% rate writes down to the residual value in the first year £100.00, £10.00, 100%, 2 → ×2
a one-year life writes off everything but the residual value £500.00, £50.00, 20%, 1 → ×1
a zero rate leaves the whole write-off to the final year £300.00, £0.00, 0%, 2 → ×2
a zero-cost asset has a schedule of zeros £0.00, £0.00, 25%, 2 → ×2
residual value equal to cost: nothing to depreciate £50.00, £50.00, 25%, 3 → ×3
yen: whole units only ¥1,000,000, ¥0, 33.33%, 3 → ×3
Show the other 7 tests
CaseArgumentsExpected
zero years is refused £10.00, £0.00, 25%, 0 → error: years must be at least 1
a rate over 100% is refused £10.00, £0.00, 100.01%, 3 → error: rate must be 0 to 10000 basis points
a negative rate is refused £10.00, £0.00, -0.01%, 3 → error: rate must be 0 to 10000 basis points
a negative cost is refused -£10.00, £0.00, 25%, 3 → error: cost must not be negative
a negative residual value is refused £10.00, -£0.01, 25%, 3 → error: residual value must not be negative
a residual value above cost is refused £10.00, £10.01, 25%, 3 → error: residual value must not exceed cost
mixed currencies are refused £10.00, €0.00, 25%, 3 → error: currency mismatch

More from the author

The other way round, a high rate can reach the residual value early. A charge is never allowed to take the value below the residual value: it is capped at the opening value less the residual, and later years charge nothing. So the closing value is never below the residual value in any year.

Each charge is rounded half-up to the minor unit (money.apply-rate), once per year, on that year's opening value; nothing is carried between years except the closing value itself, which is the balance-sheet number.

The result is the same `DepreciationPeriod` shape as finance.depreciation-straight-line, so the two schedules print and compare the same way. Full years only: part-year charges in the years of acquisition and disposal are a policy choice, left to finance.proration.

This is accounting depreciation. UK capital allowances (writing-down allowances on a pool, at 18% or 6%) look similar but are a tax computation with their own rules, and are not this function.

Files

PathBytes
README.md1,644
impl/python.py2,011
impl/rust.rs2,566
impl/typescript.ts1,870
vectors.json7,096