TheAlgorithms/Python · error · TypeError

Residual value must be numeric

Error message

Residual value must be numeric

What it means

Raised by straight_line_depreciation() when residual_value is not int or float (TypeError). residual_value participates in subtraction (purchase - residual), so non-numeric inputs fail fast before the depreciation schedule is built. It is the fourth check, after useful_years type/range and purchase_value type.

Source

Thrown at financial/straight_line_depreciation.py:65

    >>> straight_line_depreciation(4, 1001.0)
    [250.25, 250.25, 250.25, 250.25]
    >>> straight_line_depreciation(11, 380.0, 50.0)
    [30.0, 30.0, 30.0, 30.0, 30.0, 30.0, 30.0, 30.0, 30.0, 30.0, 30.0]
    >>> straight_line_depreciation(1, 4985, 100)
    [4885.0]
    """

    if not isinstance(useful_years, int):
        raise TypeError("Useful years must be an integer")

    if useful_years < 1:
        raise ValueError("Useful years cannot be less than 1")

    if not isinstance(purchase_value, (float, int)):
        raise TypeError("Purchase value must be numeric")

    if not isinstance(residual_value, (float, int)):
        raise TypeError("Residual value must be numeric")

    if purchase_value < 0.0:
        raise ValueError("Purchase value cannot be less than zero")

    if purchase_value < residual_value:
        raise ValueError("Purchase value cannot be less than residual value")

    # Calculate annual depreciation expense
    depreciable_cost = purchase_value - residual_value
    annual_depreciation_expense = depreciable_cost / useful_years

    # List of annual depreciation expenses
    list_of_depreciation_expenses = []
    accumulated_depreciation_expense = 0.0
    for period in range(useful_years):
        if period != useful_years - 1:
            accumulated_depreciation_expense += annual_depreciation_expense
            list_of_depreciation_expenses.append(annual_depreciation_expense)

View on GitHub (pinned to f5988cc097)

Solutions

  1. Pass residual as a number; use 0 when there is no salvage value.
  2. Convert None -> 0.0 at the boundary if 'unspecified' means 'no residual' in your domain.
  3. Apply the same numeric parsing you use for purchase_value — the checks are symmetric.

Example fix

# before
straight_line_depreciation(5, 1250.0, asset.get('residual'))  # None when absent

# after
straight_line_depreciation(5, 1250.0, asset.get('residual') or 0.0)
Defensive patterns

Strategy: type-guard

Validate before calling

residual = residual if isinstance(residual, (int, float)) else 0.0
straight_line_depreciation(5, 1250.0, residual)

Type guard

def is_numeric(v: object) -> bool:
    return isinstance(v, (int, float)) and not isinstance(v, bool)

Try / catch

try:
    sched = straight_line_depreciation(y, p, r)
except TypeError as exc:
    if 'Residual value' in str(exc):
        sched = straight_line_depreciation(y, p, 0.0)
    else:
        raise

Prevention

When it happens

Trigger: Calling straight_line_depreciation(5, 1250.0, '50.0'), passing None (common for 'no salvage value' defaults), or a Decimal salvage figure.

Common situations: Optional salvage-value fields defaulting to None in forms and APIs, string inputs from config files, or mixed types when purchase is float but residual comes from a different source.

Related errors


AI-assisted analysis of TheAlgorithms/Python@f5988cc097 (2026-08-14). Data as JSON: /api/errors/082decb2b6f84aa1. Report an issue: GitHub.