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 valuereducing_balance_depreciation(£999.99, £0.00, 30%, 3)→ ×3 each year's charge is rounded half-up once, on that year's opening valuereducing_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.
pub fn reducing_balance_depreciation(cost: &Money, residual_value: &Money, rate_basis_points: i64, years: i64) -> Vec<DepreciationPeriod>
| cost | Money | capitalised cost of the asset |
| residual_value | Money | expected value at the end of its life; 0 if written down to nothing |
| rate_basis_points | int | the yearly rate on the opening value, 2500 = 25%; 0 to 10000 |
| years | int | useful life in whole years, at least 1 |
| returns | DepreciationPeriod[] |
Your code names it in one line, in the file that uses it
fune!(finance.depreciation-reducing-balance@^1); // then call reducing_balance_depreciation(…)
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::finance_depreciation_straight_line::{depreciation_period_to_value, DepreciationPeriod}; ← from finance.depreciation-straight-line ^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}; ← from money.amount ^1.0.0 · built alongside by fune
use super::money_apply_rate::apply_rate; ← from money.apply-rate ^1.0.0 · built alongside by fune
/// 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.
///
/// # Panics
/// Panics if `years` is below 1, the rate is outside 0..=10000, either amount
/// is negative, the residual value exceeds the cost, or the currencies differ.
pub fn reducing_balance_depreciation(
cost: &Money,
residual_value: &Money,
rate_basis_points: i64,
years: i64,
) -> Vec<DepreciationPeriod> {
if years < 1 {
panic!("years must be at least 1, received {}", years);
}
if !(0..=10000).contains(&rate_basis_points) {
panic!("rate must be 0 to 10000 basis points, received {}", rate_basis_points);
}
if cost.minor < 0 {
panic!("cost must not be negative");
}
if residual_value.minor < 0 {
panic!("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 {
panic!("residual value must not exceed cost");
}
let mut schedule: Vec<DepreciationPeriod> = Vec::with_capacity(years as usize);
let mut opening_value = cost.clone();
for year in 1..=years {
let headroom = subtract_money(&opening_value, residual_value);
let mut charge = if year == years {
headroom.clone()
} else {
apply_rate(&opening_value, rate_basis_points, "half-up")
};
if charge.minor > headroom.minor {
charge = headroom;
}
let closing_value = subtract_money(&opening_value, &charge);
schedule.push(DepreciationPeriod {
year,
opening_value: opening_value.clone(),
charge,
closing_value: closing_value.clone(),
});
opening_value = closing_value;
}
schedule
}
pub fn fune_vector(args: &[Value]) -> Value {
Value::Arr(
reducing_balance_depreciation(
&money_from_value(&args[0]),
&money_from_value(&args[1]),
args[2].as_i64(),
args[3].as_i64(),
)
.iter()
.map(depreciation_period_to_value)
.collect(),
)
}Install
fune build
With that line in your source, in a Rust project (language rust in fune.project), fune build resolves it and its 4 dependencies, pins them in fune.lock, downloads only the Rust 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. A crate’s build.rs runs it before every compile. Or pin a range in fune.project and build in one step:
fune add finance.depreciation-reducing-balance
The manifest, vectors and README with only the Rust implementation. Install it without the registry with fune add ./finance.depreciation-reducing-balance-1.0.0-rust.fune, or fetch it from a terminal with fune pull finance.depreciation-reducing-balance@1.0.0:rust.
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.
| Case | Arguments | Expected | |
|---|---|---|---|
| 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
| Case | Arguments | Expected | |
|---|---|---|---|
| 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
| Path | Bytes |
|---|---|
| README.md | 1,644 |
| impl/python.py | 2,011 |
| impl/rust.rs | 2,566 |
| impl/typescript.ts | 1,870 |
| vectors.json | 7,096 |