Six Dollars Too Accurate

A test fixture in the tax estimator asserted that a single filer on a $100,000 salary owes $13,449 in federal income tax for 2025. I had worked that number out by hand, twice, from the published rate schedule. It is the wrong number, and the right number is higher.

The right number is $13,455. Nothing about the rate schedule produces it.

The instructions do not want the formula

Federal tax brackets are a piecewise function, and everyone who has ever written a tax calculator has written it: walk the segments, take the marginal rate on the part of income that falls in each. For a single filer in 2025 the standard deduction is $15,750, so a $100,000 salary leaves $84,250 of taxable income. Ten percent of the first $11,925 is $1,192.50. Twelve percent of the next $36,550 is $4,386.00. Twenty-two percent of the remaining $35,775 is $7,870.50. Total, $13,449.00, exactly, no rounding required.

Below $100,000 of taxable income, the 1040 instructions do not permit you to do this. They require the printed tax table.

The table is rows. Each row is a range of taxable income, $50 wide through most of its span, and each row carries a single dollar figure. That figure is the rate schedule evaluated at the midpoint of the row, rounded to the nearest dollar. Everyone whose taxable income lands anywhere in the row pays the number printed next to it.

So $84,250 does not get taxed as $84,250. It falls in the row covering $84,250 up to $84,300, whose midpoint is $84,275. The tax on $84,275 is $13,454.50, which the table prints as $13,455.

Twenty-five dollars you do not have

That filer sits at the exact floor of the row. The table charges them tax on $25 of income that does not exist. At a 22% marginal rate that is $5.50, and the rounding carries it to $6.

Someone $49 richer, at the top of the same row, pays the identical $13,455 and comes out $5.50 ahead of the formula. Both of them filed correctly. The table is not an approximation anyone is expected to correct; it is the operative instruction, and the error is symmetric by design because a table has to have rows.

The rows are not uniform. They narrow toward the bottom: $25 wide from $25 to $3,000, then $10 wide beneath that, and below $5 of taxable income the tax is zero. And the whole regime stops at $100,000. Above that ceiling you use the rate schedule directly, unrounded, and the arithmetic I had written becomes the correct arithmetic again. The rounding applies to the people underneath.

The test got stricter by getting wronger

The fix was a function called tableTax that reconstructs the row you are standing in, evaluates the schedule at its midpoint, and rounds. It wraps bracketTax rather than replacing it, because above $100,000 and inside the alternative-minimum comparison the raw schedule is still what the form wants. Three call sites changed: federal ordinary income, California ordinary income, and the regular-tax baseline the AMT band measures its gap against.

The part I did not expect was what happened to the tests.

The old assertion could not be exact. It read close(r.incomeTax, 13449), a helper with a tolerance, because a chain of floating-point multiplications through several bracket segments does not reliably land on a round number. The new assertion is assert.equal(r.incomeTax, 13455). No tolerance. The table’s output is an integer by construction, so once the engine emulated the rounding procedure, every downstream number became exactly comparable.

The precise version of the calculation needed a fuzzy test. The rounded version could be pinned to the dollar. I went looking for fidelity to the instructions and got determinism thrown in.

Eight hand-worked returns now sit in a golden file, each derived longhand from the revenue procedures and the state schedules rather than from the engine’s own output, which is the only way a golden file means anything. Wages, capital-gain stacking, self-employment with the qualified-business deduction, the investment-income surtax, California’s mental-health surcharge and its credit phaseout.

Six dollars does not look like a bug

I had been treating the tax code as the specification and the printed table as a rendering of it, a convenience for people without a calculator. It is the reverse. For anyone under that ceiling the table is the instruction, and the rate schedule is the derivation behind it. A tool that computes the derivation and reports it has not been more rigorous. It has answered a question nobody asked, and it will disagree with the return its user actually files on every line that matters to them.

That gap is small enough to be worse than a large one. Six dollars looks like a rounding difference, the kind of thing you shrug at, right up until someone reconciles this tool against their filing software and finds two authoritative-looking numbers that do not match. Then the whole tool is in question, correctly, because a tax estimator’s only claim is agreement.

When your program’s output has to line up with an institution’s, the institution’s procedure is the spec. Not the law it implements, not the mathematics it approximates. The procedure, including its rounding, including the places it is knowingly imprecise.

The row is $50 wide. The filer at the bottom of it pays for $25 they never earned, the filer at the top gets $25 free, and my job is to print the same number the table prints for both of them.


← all writing