Skip to content

Float precision loss when aggregating multi-year budget-window totals #451

@MaxGhenis

Description

@MaxGhenis

Summary

Budget-window impact fields are typed float and summed with += across up to 75 years. At trillion-dollar magnitudes this accumulates meaningful precision loss — the totals and the per-year impacts in the response will not be self-consistent to the cent (or even to the dollar).

Location

  • projects/policyengine-api-simulation/src/modal/budget_window_results.py:54-72
  • projects/policyengine-api-simulation/src/modal/gateway/models.py:113-128

What goes wrong

class BudgetWindowAnnualImpact(BaseModel):
    year: str
    taxRevenueImpact: float
    federalTaxRevenueImpact: float
    ...

def sum_annual_impacts(annual_impacts):
    totals = {"taxRevenueImpact": 0, ...}
    for annual_impact in annual_impacts:
        totals["taxRevenueImpact"] += annual_impact.taxRevenueImpact
        ...
    return BudgetWindowTotals(**totals)

Federal revenue impacts in the trillions sum over 75 years exceed 2^53 and quickly lose integer precision in IEEE-754 double. Even at sub-trillion magnitudes, the naive += accumulation introduces drift that makes the reported totals.budgetaryImpact disagree with sum(annualImpacts.budgetaryImpact) when clients re-check.

Suggested fix

  • Model impacts as decimal.Decimal or integer cents at the boundary; convert to float only for display.
  • Or use math.fsum(...) in sum_annual_impacts to reduce accumulation error.
  • Add a test that sums 75 years of trillion-dollar impacts and asserts totals equal the exact sum.

Severity

Medium. Silent numerical drift in public-facing budget numbers.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions