Tool

Does your engine return the same money as the spreadsheet?

Four implementations of the same rounding rule, one input, different answers, on ordinary values rather than contrived ones. Paste what your engine actually sees and find where they part company.

Where the money is rounded to.
Used only to turn the divergence rate into a count.
Values, exactly as they are stored upstream
Value as writtenRemove
6 rows
Paste stored values, not an exported column. An export writes what the cell displays, which has already been rounded by the thing you are trying to test.
Values checked
6
Where implementations disagree
6
At this volume
40,000 a year
100.0% of the sample
These four implementations of the same rule return different money. Pin the rounding mode explicitly in your engine and prove parity at stored precision, because a one-unit difference on a binding quote is an exposure rather than a cosmetic defect.
The same value, four ways of rounding it
ValueHalf away from zeroHalf to eventoFixed on a doublemultiply, round, divideApart by
2.1552.162.162.152.151 unit
1.0051.011.001.001.001 unit
8.4758.488.488.478.481 unit
0.1250.130.120.130.131 unit
2.6752.682.682.672.681 unit
1.1151.121.121.111.121 unit
The first column is what a spreadsheet produces. The last two go through a double, where the value you wrote is frequently not the value stored.
Why the float columns differ
2.155 is stored as 2.1549999999999998, which is below 2.155, so a rule that rounds half up never sees a half to round.
1.005 is stored as 1.00499999999999989, which is below 1.005, so a rule that rounds half up never sees a half to round.
8.475 is stored as 8.47499999999999964, which is below 8.475, so a rule that rounds half up never sees a half to round.
0.125 is held exactly, so the float columns see a real tie. The split here is the tie-breaking rule alone: half away from zero goes up, half to even goes down.
2.675 is stored as 2.67499999999999982, which is below 2.675, so a rule that rounds half up never sees a half to round.
Proving parity before a quote is binding

The harness reads stored values rather than an export, pins the rounding mode explicitly, and compares to the unit against quotes the business already issued.

Your inputs are included so the reply can be specific.
This calculator runs entirely in your browser. Nothing you type is sent anywhere unless you ask for the result by email at the bottom of the page.

Rounding 2.15 to one decimal place

One rule, four implementations, two answers. Every one of these is a correct implementation of something, which is why the divergence survives review.

ImplementationResultWhy
Half away from zero2.2What a spreadsheet does. A true half always moves away from zero.
Half to even2.2Ties go to the even digit, which removes the upward bias across many values.
toFixed on a double2.1Rounds the stored value, 2.1499999999999999, where there is no tie to break.
Multiply, round, divide2.2The multiply lands exactly on 21.5, so the tie returns and goes up.

The float rows go through a double, where the value you wrote is frequently not the value stored.

Why this happens

Rounding to two decimal places sounds like one operation. It is at least four, and they disagree on ordinary money rather than on edge cases somebody constructed.

The first split is the rule itself. Half away from zero sends a true half away from zero, which is what a spreadsheet does and what most people mean by rounding up. Half to even sends it to the nearest even digit, removing the upward bias you accumulate by always rounding halves up.

The second split is not about the rule at all. A value written as 2.15 is not stored as 2.15 in a binary floating-point number. It is stored slightly below it.

2.15 as a double is 2.14999999999999991118215802999

So a correct implementation of round half up, applied to the stored value, finds no half and goes down. The rule was right. The input was not the one you wrote.

Then the two float idioms disagree with each other, which catches people who already knew about the first two. toFixed rounds the stored value and goes down. Multiplying by ten first produces exactly 21.5, because that multiply rounds too, and the tie comes back.

The trap in the test

The natural way to prove your engine agrees with the business is to export the historic figures and compare. That test cannot find this class of defect.

A spreadsheet writes CSV as the displayed value, not the stored one, so the export has already had the spreadsheet’s rounding applied to it. The harness compares your engine against a number rounded by the mechanism under test, and agrees with itself whatever your engine does at the boundary.

Read stored values at full precision, pin the rounding mode explicitly rather than inheriting a language default, and compare to the unit against figures the business actually issued. The shape of that harness is in the piece on automating a gatekeeper away, and the related case of a value that is schema-valid and still wrong is in units are semantics, not syntax.

Common questions

Why does a spreadsheet round 2.15 to 2.2 and my code round it to 2.1?

Two separate reasons that happen to point the same way. A spreadsheet rounds half away from zero, so a true half goes up. Your code is probably working on a double, where 2.15 is stored as 2.1499999999999999, which is below the half, so a correct implementation of round half up correctly rounds it down. Neither is a bug in isolation. The divergence between them is the bug.

Why can two float idioms in the same language disagree with each other?

Because they round at different points. Calling toFixed rounds the stored double, which sits just under the half, so it goes down. Multiplying by a power of ten first can land exactly on the half, because the multiply itself rounds, and then the tie reappears and goes up. Same language, same input, two answers, and neither path is incorrect by its own rules.

Why can a parity test built from a CSV export not find this?

Because a spreadsheet writes CSV as the displayed value rather than the stored one, so the export has already had the spreadsheet's rounding applied. The harness then compares your engine against a number that was rounded by the exact mechanism under test. It agrees with itself and passes whatever your engine does at the boundary.

Which rounding mode should a pricing engine use?

Whichever one the business already uses, which is usually half away from zero, because that is what the workbook does. The important part is that the mode is stated explicitly in the engine rather than inherited from whatever the language or the database defaults to. An unstated rounding mode is a decision nobody made.

Is a one-unit difference worth this much attention?

It is when somebody is bound by the number. A cent on an indicative price is cosmetic. A cent on an executable quote, or on a figure a professional certifies, is an exposure on every transaction, and it is exactly the size of error that survives review because it reads as a rounding artefact rather than as a fault.

How many of my values will actually land on a boundary?

Fewer than you fear and more than none, and the rate depends on how the values are produced upstream. Figures derived from a percentage or a division land on boundaries far more often than typed ones. Run your real values through rather than estimating, which is what the volume field is for.

The honest limit

This compares four implementations on the values you paste. It does not know your database column type, your language runtime, or where in your pipeline the rounding happens, and a real system usually rounds more than once. A value that agrees here can still diverge in your stack if it is rounded twice or passes through a float column on the way. The useful output is a list of boundary values to run against your own engine, not a verdict on it.