Financial functions

VDB

Matches all 2,210 recorded answers

Recorded: 2,201 in the answer bank and 9 workbook cells. How we measure this · See the Rust code

Local names in 40 languages
LanguageName in Excel
ArabicVDB
BulgarianVDB
CzechODPIS.ZA.INT
DanishVSA
GermanVDB
GreekVDB
English (Australia)VDB
English (United Kingdom)VDB
English (United States)VDB
SpanishDVS
EstonianVDB
FinnishVDB
FrenchVDB
HebrewVDB
CroatianVDB
HungarianÉCSRI
IndonesianVDB
ItalianVDB
JapaneseVDB
KoreanVDB
LithuanianVDB
LatvianVDB
NorwegianVERDIAVS
DutchVDB
PolishVDB
Portuguese (Brazil)VDB
Portuguese (Portugal)BDV
RomanianVDB
RussianПУО
SlovakVDB
SlovenianVDB
Serbian (Latin)VDB
SwedishVDEGRAVSKR
ThaiVDB
TurkishDAB işlevi
UkrainianVDB
VietnameseVDB
Chinese (Simplified)VDB
Chinese (Hong Kong)VDB
Chinese (Traditional)VDB

Names from Microsoft's support site.

What it does

VDB(cost, salvage, life, start_period, end_period, [factor], [no_switch])

VDB(cost, salvage, life, start_period, end_period, [factor], [no_switch]) returns the depreciation of an asset over the span from start_period to end_period, by a declining balance method. The name stands for variable declining balance.

Two things set it apart from DDB. It covers a span and not one period, and that span can start and end at fractions of a period. And it switches to straight line when the declining charge falls behind, unless told not to.

The underlying schedule works period by period from a book value that starts at cost:

declining(k)  =  book(k−1) × factor / life,  limited to book(k−1) − salvage
straight(k)   =  (book(k−1) − salvage) / (life − (k−1))

charge(k)     =  the larger of declining(k) and straight(k)      no_switch FALSE
charge(k)     =  declining(k)                                    no_switch TRUE

book(k)       =  book(k−1) − charge(k)

Microsoft states the switch in one sentence: Excel switches to straight line when depreciation is greater than the declining balance calculation. Once the schedule has switched it stays on the straight line, because the remaining basis is then spread evenly over the remaining life.

start_period and end_period are elapsed time, not period numbers. The first full period is the span from 0 to 1:

  • VDB(c, s, l, 0, 1) is the first period's charge.
  • VDB(c, s, l, 1, 1) is 0, an empty span. It is not the first period.
  • VDB(c, s, l, 0, l) is the whole life.

A worked schedule, by the rule above, for cost 1000, salvage 0, life 5, factor 2:

PeriodBook at startDecliningStraight lineCharge with switchCharge with no_switch TRUE
11000.00400.00200.00400.00400.00
2600.00240.00150.00240.00240.00
3360.00144.00120.00144.00144.00
4216.0086.40108.00108.0086.40
5108.0043.20108.00108.0051.84
Total1000.00922.24

The book and declining columns follow the charge with the switch. The switch lands in period 4, and with it the asset is depreciated down to salvage exactly. Without it 77.76 is never charged. That shortfall is why the switch exists. This table is the Handbook's arithmetic from the rule, not a reading.

Microsoft's six examples, for cost 2400, salvage 300 and a life of 10 years, all follow the schedule, with fractional spans prorated inside a period as described below:

FormulaMeaningResult
=VDB(2400, 300, 3650, 0, 1)First day1.32
=VDB(2400, 300, 120, 0, 1)First month40.00
=VDB(2400, 300, 10, 0, 1)First year480.00
=VDB(2400, 300, 120, 6, 18)Month 6 to month 18396.31
=VDB(2400, 300, 120, 6, 18, 1.5)The same at factor 1.5311.81
=VDB(2400, 300, 10, 0, 0.875, 1.5)First fiscal year, part year, factor 1.5315.00

The last example shows the proration: a full first year at factor 1.5 is 360, and seven eighths of that is 315.

Arguments

ArgumentMeaningDefault
costThe initial cost of the asset. Required.none
salvageThe value at the end of the depreciation. Can be 0. Required.none
lifeThe number of periods over which the asset is depreciated. Required.none
start_periodThe start of the span, in the same units as life. Required.none
end_periodThe end of the span, in the same units as life. Required.none
factorThe rate at which the balance declines. Optional.2 if left off the end of the call
no_switchTRUE to stay on declining balance; FALSE to allow the switch. Optional.FALSE
  • An empty factor slot is not the default. Microsoft says an omitted factor is 2. The notes below record that this holds only when the argument is left off the end of the call. An empty slot, as in VDB(c, s, l, a, b, , FALSE), is read as 0, which gives the straight-line amount. To reach no_switch while keeping double declining, write the 2.
  • A blank cell as factor behaves like the empty slot. In the note's example a blank cell, and a cell holding 0, both give the same 90 as the empty slot. A cell holding -1 gives #NUM!.
  • Text in factor and no_switch is treated differently in each slot. factor reads "2" as 2, and gives #VALUE! for "TRUE", "FALSE" and "". In the no_switch slot of the same recorded run, "TRUE" and "FALSE" are accepted, and "2" and "" give #VALUE!.
  • Units must match. life, start_period and end_period are in the same units. Monthly depreciation of a ten-year asset uses life 120, as in the examples above.

Try it

Input cells (optional)

Example: {"B2": 3, "B3": 4}. A1 holds the formula. Other cells need values before a formula can read them.

Enter a formula, then calculate.

Runs locally in your browser with OneCalc. Its answer may differ from Excel. Use English function names and commas between arguments.

Quirks

Result and edge cases

Returns Number.

Recorded behaviour, from readings on Excel 16.0 build 20430, Windows 64-bit, CPU not recorded, classified for this page:

  • start_period = end_period gives 0 when the span lies inside the life.
  • factor = 0 gives straight line. With no_switch FALSE and cost above salvage, 10,427 of 10,686 recorded answers are within 1e−9 relative of (cost − salvage) / life × (end − start). The other 259 all have cost below 1e−38. With no_switch TRUE, all 9,322 recorded answers are 0, because there is nothing to switch to and the declining rate is zero.
  • salvage above cost gives a negative number, not an error: VDB(1, 1000, 1, 0, 0.5, 2, FALSE) is -499.5.
  • cost = 0 is admitted, and with a positive salvage also gives a negative number: VDB(0, 1, 1, 0, 0.5, 2, FALSE) is -0.5.
  • salvage = 0 is admitted, as Microsoft's parameter table says.
  • Inputs near the limits of the number range can give #NUM! with no other condition met, for example with an enormous factor.

The span is not additive. This is the most important behaviour on the page, and the arithmetic of the schedule would not lead you to expect it. For cost 1000, salvage 100, life 1.5 and factor 1:

SpanExcel
0 to 1.25783.3333333333333
1.25 to 1.5146.66666666666669
Sum of the two930.0
0 to 1.5900.0

Splitting a span and adding the pieces does not give the whole. The recorded research on these readings concludes that a fractional part of start_period is applied first, which changes the basis on which later whole periods decline. So a depreciation schedule built from consecutive fractional spans need not total cost − salvage.

Where this applies can be read from the recorded answers. Taking every case where a span and its two pieces were all read, and calling a split additive when the pieces sum to the whole within 1e−9 relative:

PathFirst piece startsSplit pointAdditive
no_switch TRUEanyany374 of 374
switch allowedwhole periodwhole period837 of 837
switch allowedwhole periodfraction715 of 1,002
switch allowedfractionwhole period410 of 456
switch allowedfractionfraction161 of 286

So the behaviour belongs to the switching path, and appears only when one of the pieces starts at a fraction. A schedule of whole periods adds up.

Errors

Microsoft documents no error values for VDB. It says only that "All arguments except no_switch must be positive numbers", which is contradicted in the same place by the statement that salvage can be 0, and by readings: cost, salvage, start_period and factor can each be 0.

An empty factor slot in VDB is 0, not the default 2

VDB defaults factor to 2 only when the argument is left off the end of the call. An empty slot, as in VDB(c,s,l,a,b,,FALSE), is read as 0, and the result is the straight-line amount: 90 here, where factor 2 gives 160. Text factors other than numbers give #VALUE!, including "TRUE" and "FALSE", while "2" is read as 2.

FormulaExcelMathematical value
=VDB(A100,A101,A102,A103,A104,,FALSE)
Excel build not recorded in this run; bulk captures of the same date (2026-09-29) record Excel 16.0 build 20430, Windows 64-bit, workbook compatibility 2
Inputs and reference value

Number supplied through a cell.

cost: 1000
0x408f400000000000

salvage: 100
0x4059000000000000

life: 10
0x4024000000000000

start_period: 1
0x3ff0000000000000

end_period: 2
0x4000000000000000

90
0x4056800000000000
—
=VDB(A100,A101,A102,A103,A104,"2",FALSE)
Excel build not recorded in this run; bulk captures of the same date (2026-09-29) record Excel 16.0 build 20430, Windows 64-bit, workbook compatibility 2
Inputs and reference value

Number supplied through a cell.

cost: 1000
0x408f400000000000

salvage: 100
0x4059000000000000

life: 10
0x4024000000000000

start_period: 1
0x3ff0000000000000

end_period: 2
0x4000000000000000

160
0x4064000000000000
—
=VDB(A100,A101,A102,A103,A104,"TRUE",FALSE)
Excel build not recorded in this run; bulk captures of the same date (2026-09-29) record Excel 16.0 build 20430, Windows 64-bit, workbook compatibility 2
Inputs and reference value

Number supplied through a cell.

cost: 1000
0x408f400000000000

salvage: 100
0x4059000000000000

life: 10
0x4024000000000000

start_period: 1
0x3ff0000000000000

end_period: 2
0x4000000000000000

#VALUE!—

Inputs, build details and evidence

How to implement

Numerical notes

The notes below cover how the arguments are read. They say nothing about the arithmetic of the result, so this section gives the shape of that arithmetic and the last-unit questions remain open.

The schedule is a recurrence. Period k needs the book value after k − 1 charges, so the cost grows with the period and each earlier rounding is inherited. In exact arithmetic the declining part has a closed form, cost × (1 − factor/life)^k, but it is not the same floating-point number as the recurrence, and it does not know about the limit at salvage or the switch.

Tiny values. With factor 0 and very small costs, recorded answers depart from the straight-line amount, and some are 0 where that amount would be a subnormal number. This is consistent with intermediate results being published as zero when they fall below the normal range, but the arithmetic is not established here.

No transcendental functions. Unlike DB, VDB needs only addition, subtraction, multiplication, division and comparison. Any last-unit difference is therefore about the order and precision of those operations.

Rust code

Show the CalcFunc source of this function

Quoted from the public repository DnaCalc/DnaCalc, crates calc-func and calc-fml, at commit 9e1884369a67. Comments are the source's own. A kernel shared with other functions appears once, on its module page.

VDB · 10 lines · calc-func/src/catalogue/financial.rs on GitHub
25 vdb_fn "VDB" (5, 7) [V "cost", V "salvage", V "life", V "start_period", V "end_period", V "factor", V "no_switch"] |s, a, sv| {
    // W111 typed and padding controls read the required arguments, then factor,
    // then no_switch. An explicit empty factor slot coerces to zero; only an
    // absent trailing factor takes the default of two.
    let (cost, salvage, life, start, end) =
        (num(s, a, 0)?, num(s, a, 1)?, num(s, a, 2)?, num(s, a, 3)?, num(s, a, 4)?);
    let factor = if a.len() > 5 { num(s, a, 5)? } else { 2.0 };
    let no_switch = if given(a, 6) && !a[6].is_empty() { flag(s, a, 6)? } else { false };
    out(s, dep::vdb_kernel(cost, salvage, life, start, end, factor, no_switch))
}
declining_interval_depreciation · 54 lines · calc-func/src/ported/depreciation_family.rs on GitHub
fn declining_interval_depreciation(
    cost: f64,
    salvage: f64,
    life: f64,
    start_period: f64,
    end_period: f64,
    factor: f64,
    no_switch: bool,
) -> Result<f64, WorksheetErrorCode> {
    validate_finite(&[cost, salvage, life, start_period, end_period, factor])?;
    if cost < 0.0 || salvage < 0.0 || life <= 0.0 || factor < 0.0 {
        return Err(WorksheetErrorCode::Num);
    }
    if start_period < 0.0 || end_period < start_period || end_period > life {
        return Err(WorksheetErrorCode::Num);
    }
    if no_switch {
        if start_period == end_period {
            return Ok(0.0);
        }
        let mut year = start_period.floor();
        let mut total = 0.0;
        while year < end_period {
            let dep = ddb_period_depreciation(cost, salvage, life, year + 1.0, factor)?;
            // Excel rounds these partial products separately, even when both
            // bounds lie in the same year. The final fraction is formed by
            // subtracting the unused tail from one; end-year loses observed
            // cancellation, including tiny negative intervals. The final year uses the same
            // annual amount, without rescaling it by the remaining year length.
            let final_fraction = 1.0 - ((year + 1.0) - end_period).max(0.0);
            // Both products pass through an extended-precision store. Their
            // separate rounding is visible when adjacent endpoints cancel.
            let final_part =
                publish_depreciation(crate::ported::excel_numeric::excel_x87_mul(dep, final_fraction))?;
            let initial_part = publish_depreciation(crate::ported::excel_numeric::excel_x87_mul(
                dep,
                (start_period - year).max(0.0),
            ))?;
            total += final_part - initial_part;
            if dep == 0.0 {
                break;
            }
            year += 1.0;
        }
        // The partial products publish subnormals as zero, but subtraction
        // of two normal products may return a nonzero subnormal Value2.
        return if total.is_finite() {
            Ok(total)
        } else {
            Err(WorksheetErrorCode::Num)
        };
    }
    switched_interval_depreciation(cost, salvage, life, start_period, end_period, factor)
}
switched_interval_depreciation · 109 lines · calc-func/src/ported/depreciation_family.rs on GitHub
fn switched_interval_depreciation(
    cost: f64,
    salvage: f64,
    life: f64,
    start_period: f64,
    end_period: f64,
    factor: f64,
) -> Result<f64, WorksheetErrorCode> {
    use crate::ported::excel_numeric::{
        excel_x87_add as add, excel_x87_div as div, excel_x87_mul as mul, excel_x87_sub as sub,
    };
    if start_period == end_period {
        return Ok(0.0);
    }
    // Exact neighboring probes pin an inclusive absolute 2^-1026 boundary.
    // This is a VDB switch rule, not a general numeric comparison policy.
    let switch_exceeds = |straight: f64, declining: f64| {
        straight > declining && straight - declining >= f64::MIN_POSITIVE / 16.0
    };
    let rate = publish_depreciation(div(factor, life))?;
    let initial_declining = publish_depreciation(mul(cost, rate))?;
    let initial_straight = publish_depreciation(div(cost - salvage, life))?;
    if cost >= salvage && switch_exceeds(initial_straight, initial_declining.min(cost - salvage)) {
        return publish_depreciation(mul(initial_straight, end_period - start_period));
    }
    let fraction = start_period - start_period.floor();
    let initial_amount = initial_declining.min(cost - salvage).max(0.0);
    let initial_part = publish_depreciation(mul(initial_amount, fraction))?;
    let mut book = sub(cost, initial_part);
    let mut remaining_life = life - fraction;
    let mut straight_line = None;
    let mut accumulate = |duration: f64, requested: bool| -> Result<f64, WorksheetErrorCode> {
        let mut total = 0.0;
        let mut cursor = start_period;
        let mut year = 0.0;
        loop {
            if if requested {
                cursor >= end_period
            } else {
                year >= duration
            } {
                break;
            }
            let declining = publish_depreciation(mul(book, rate))?;
            let available = book - salvage;
            let denominator = if declining > available {
                remaining_life.max(1.0)
            } else {
                remaining_life
            };
            let straight = match straight_line {
                Some(value) => value,
                None => publish_depreciation(div(available, denominator))?,
            };
            // Even clipping does not hide overflow of this remaining-life
            // division. A fixed straight-line amount does not waive it either.
            publish_depreciation(div(available, remaining_life))?;
            let amount = if straight_line.is_none() && declining > available {
                available
            } else if switch_exceeds(straight, declining.min(available)) {
                straight_line = Some(straight);
                straight
            } else {
                declining.min(available)
            };
            let remaining = if requested {
                end_period - cursor
            } else {
                duration - year
            };
            let take = if straight_line.is_some() {
                remaining
            } else {
                remaining.min(1.0)
            };
            // A whole clipped year preserves a subnormal amount. Fractional
            // products publish separately, so a blanket final flush is wrong.
            let part = if take == 1.0 {
                amount
            } else {
                publish_depreciation(mul(amount, take))?
            };
            total = add(total, part);
            book = if requested {
                book - part
            } else {
                sub(book, part)
            };
            remaining_life -= take;
            // Use the already stored remaining length: cursor+1 can round
            // below end even when this subtraction has rounded to exactly one.
            if requested && remaining <= 1.0 {
                break;
            }
            cursor += 1.0;
            if straight_line.is_some() {
                break;
            }
            year += 1.0;
        }
        if !total.is_finite() {
            Err(WorksheetErrorCode::Num)
        } else {
            Ok(if total == 0.0 { 0.0 } else { total })
        }
    };
    accumulate(start_period.floor(), false)?;
    accumulate(end_period - start_period, true)
}
vdb_kernel · 19 lines · calc-func/src/ported/depreciation_family.rs on GitHub
pub fn vdb_kernel(
    cost: f64,
    salvage: f64,
    life: f64,
    start_period: f64,
    end_period: f64,
    factor: f64,
    no_switch: bool,
) -> Result<f64, WorksheetErrorCode> {
    declining_interval_depreciation(
        cost,
        salvage,
        life,
        start_period,
        end_period,
        factor,
        no_switch,
    )
}

Kernels shared with other functions

Argument and result plumbing

No implementation has been published with a Handbook test suite for this function.

Version differences

The recorded environments below distinguish a reading from a same-input comparison. Counts describe captured inputs in each environment; overlapping captures and comparisons are not totals of unique worksheet calls.

Excel 16.0 buildPlatform and CPUCaptured inputs
20430Windows, bitness not recorded, CPU not recorded106
20430Windows, 64-bit, CPU not recorded166,843

The record contains no same-input comparison between these environments. Reading different inputs on different builds does not establish either a version change or cross-build agreement.

Recorded environments and comparison details.

Excel 16.0 build 20430

Windows, bitness not recorded, CPU not recorded

106 captured inputs are indexed for this environment. Capture dates: 2026-09-29 to 2026-09-29. This is a reading count, not a comparison with another build.

Excel 16.0 build 20430

Windows, 64-bit, CPU not recorded

166,843 captured inputs are indexed for this environment. Capture dates: 2026-09-29 to 2026-09-29. This is a reading count, not a comparison with another build.

Alternatives

Relationships

  • SLN is the straight line. VDB with factor 0 and the switch allowed gives the SLN amount times the length of the span, as above.
  • DB is declining balance at a derived, rounded rate, with no factor and no switch, and a partial first year set in months.
  • SYD is sum-of-years digits, the other accelerated method in the family.
  • AMORLINC and AMORDEGRC are the French accounting pair, which prorate on a different basis.
  • Confused with: tax depreciation tables, which fix their own conventions for the year of purchase. VDB with a fractional span can approximate one, and is not the same thing.

Named variants

No named variants are recorded yet.

Suggest a correction or contribute an implementation · For agents