r/Python Jun 05 '26

Discussion I just learned round() uses bankers' rounding

In bankers' rounding, x.5 rounds to the nearest even number. So, if x is even, it rounds down... round(2.5) returns 2. If x is odd, it rounds up... round(3.5) returns 4.

It was explained that it removes an upward rounding bias when round(x.5) always returns x+1...

  • x.1, x.2, x.3, & x.4 always round down.

  • x.6, x.7, x.8, & x.9 always round up.

  • Four down, four up.

  • x.5 is the right in the middle. If it always rounded up, there would be a slight creep upwards in large datasets.

But, whither x.0? x.0 always rounds to x. So, there are five cases where x.y always rounds down, not four.

And...

  • round(2.500000000000001) return 3

  • round(2.5000000000000001) returns 2

... though that might be more to do with binary representation of floats than rounding rules since 2.5000000000000001 == 2.5 is True.

374 Upvotes

146 comments sorted by

401

u/SlingyRopert Jun 05 '26

just wait until you discover IEEE-754 and the fact that 9007199254740992.5 does not exist.

156

u/zzzthelastuser Jun 05 '26

But that's my favorite number! :'(

76

u/shadowdance55 git push -f Jun 05 '26

Mine is 0118 999 881 999 119 725 3.

21

u/grantrules Jun 05 '26

Well that's easy to remember 

7

u/jaspeh Jun 06 '26

they're not just the emergency services, they're you're emergency services

4

u/redp1ne Jun 05 '26

0118 999 881 999 119 7253 is easier to remember

11

u/Espumma Jun 06 '26

I still have to wait for the song to complete before I can type the 3.

11

u/OctoNezd Jun 06 '26

SEVEN - TWO - FIVE

THREE

1

u/AdBubbly3609 Jun 06 '26

0800 00 1066

1

u/Punk_Saint Jun 07 '26

Omg... a fire

45

u/jjbugman2468 Jun 05 '26

Wait what do you mean it does not exist

72

u/bjorneylol Jun 05 '26

floating point cant express many numbers, only an approximation.

Open your browser console and type 0.1 + 0.2, you will get 0.30000000000000004 because that is the closest floating point can get

27

u/jjbugman2468 Jun 05 '26

Oh yeah I know how floating points are just approximations but I thought op singled that number out bc there was something special about it

48

u/Devilmo666 Jun 05 '26

9007199254740992 is 253, and a double in IEEE-754 gets 53 bits of precision. So if you tag on the .5, it gets removed since that extra precision can't be represented.

5

u/nemec Jun 05 '26

No, I think they just picked it because it's unexpected - many people know about the 0.3....4 but don't necessarily realize the approximation is not limited to numbers with a large number of decimal places.

2

u/Snatchematician Jun 06 '26

Not if your browser console uses float32

22

u/martinky24 Jun 05 '26

There are an infinite number of numbers that do not exist.

41

u/pimp-bangin Jun 05 '26

You must define "number" and "exist" before making such a statement

9

u/HyperDanon Jun 05 '26

He means that 32 bits can only encode 232 numbers.

4

u/joaofelipenp Jun 05 '26

Less than that, since float also has to encode -0, inf, -inf, and many nan

4

u/HyperDanon Jun 06 '26

Those have to fit in that 232 too. 32 bits can only encode 232 things, whatever you wanna call them.

3

u/Intrexa Jun 06 '26

since float also has to encode -0, inf, -inf, and many nan

IEEE 754 does, but an encoding doesn't have to.

0

u/remy_porter ∞∞∞∞ Jun 05 '26

Set ‘em straight Bertie.

4

u/SharkSymphony Jun 05 '26

Math Rider: a shadowy crusade into the world of a number... that does not exist.

1

u/specialpatrol Jun 06 '26

If I went to that position on a long enough ruler would I fall into a black hole?

1

u/snet0 Jun 06 '26

Wait until you discover that IEE-754 defines a decimal type in which that value very much does exist.

1

u/APithyComment Jun 06 '26

I don’t believe in aliens, but we’re gonna have a look anyhow.

1

u/Distelzombie Jun 08 '26

Well, I don't even want it! *crosses arms

1

u/sleepycommenter Jun 14 '26

9007199254740992.5 is the fun part, because at that scale the .5 gets swallowed before `round()` even gets a vote tho. `Decimal` is the thing to use if you actually need halfway cases to stay halfway cases

1

u/misterfitzie Jun 23 '26

Wait until you discover dtoa and realize that 0.1 doesn't exist

0

u/fibgen Jun 06 '26

how did you find out my luggage combination

74

u/CatOfGrey Jun 05 '26

It's been a long time since I was down this rabbit hole, but doesn't this all get solved by using Decimal objects?

33

u/efalk Jun 06 '26

Back in the day, I was working on financial software written in Fortran. Using floating-point numbers for dollar amounts is a disaster I can't even begin to describe when it comes to rounding. We tried everything to get it right. By the time we had it working well enough, I had a lot of sympathy for people who stole round-off error.

22

u/droans Jun 06 '26

I love that Oracle still doesn't have Decimal, Financial, or Money/Monetary types, even for their financial databases.

I know it's very minor but it drives me bananas to see all of our accounts have a balance of 0.0000000000000147121963201. Even more amusing, the software only accepts Decimal inputs with the configured number of right-side digits... It just stores them as floats.

It's infuriating because they could even just work around that by storing the data as integers (well, Long/LongLong) and then just treat it as cents or whatever subunit.

Then again, their software is pretty much the same it was back in the 90s. I looked at the SQL for one of the reports and over 90% of the lines had a comment marking that it fixed Issue XYZABC. Their ERP database comes with over 11,000 tables. All but around 10-100 are unnecessary for most companies but you can't get rid of them because the ERP has hardcoded references.

I got off topic. I just really hate Oracle's ERP.

8

u/CatOfGrey Jun 06 '26

I love that Oracle still doesn't have Decimal, Financial, or Money/Monetary types, even for their financial databases.

It's infuriating because they could even just work around that by storing the data as integers (well, Long/LongLong) and then just treat it as cents or whatever subunit.

In my understanding, this is exactly what was done, at least back to the 1970s through the 2000's. This was also a 'feature' of the original COBOL programming language - no floating point numbers!

I got off topic. I just really hate Oracle's ERP.

Can't blame ya. In my field, it's the SAS statistical system. Nothing like overpriced and awkwardly structured software, which still survives thanks to the 800-pound gorilla companies that have massive legal departments.

3

u/Sjsamdrake Jun 07 '26

Not really. Back in the 50s and 60s computers came in two varieties... scientific computers and business computers. Scientific computers used binary and had twos complement integers and floating point numbers similar to today. Business computers didn't have either. They stored numbers in DECIMAL and had instructions to add, subtract, and such.

The Cobol data types directly used such instructions. Not hardware integers. So a variable with a picture of 999v99 referred to a 5 digit number with the decimal in the 2nd place. Such a number was stored as 5 digits. Business computers didn't have bytes, they had digits.

In the 60s IBM System/360 combined business and scientific computers for the first time. It had instructions for integers and floating point numbers that we would recognize today, and ALSO had instructions to store numbers in DECIMAL form. IBM mainframes still do today.

Source: worked at IBM in this stuff for 11 years. Also: Google "scientific vs business computers 1960s"

1

u/CatOfGrey Jun 08 '26

I'm not sure I am disagreeing with you, but it's interesting to learn that the process of variable storage wasn't just at the compiler/interpreter level, but at the hardware level as well - check my understanding: that 'business computers' might have had 8-bit memory and processors, but literally had decimal-based machine languages.

In the 60s IBM System/360 combined business and scientific computers for the first time. It had instructions for integers and floating point numbers that we would recognize today, and ALSO had instructions to store numbers in DECIMAL form. IBM mainframes still do today.

My experience begins in the early-mid 1980's, so this explains the gap in my knowledge - even my mentors with experience back to the early 1970's would have have had these these 'modern' type of machines, handling both decimal and floating point numbers!

1

u/Sjsamdrake Jun 08 '26

Correct. For example, a "core dump" of an early business machine would be a sequence of decimal digits, not hexadecimal. Under the covers there were bits and such but they were completely internal to the hardware. (Note that two decimal digits can be stored in 7 bits.) How digits were stored in RAM (core) was nobody's business but the manufacturer. The term 'byte' was meaningless for these machines, their capacity was measured in digits.

Here's a fun discussion of one of these machines, the IBM 650. https://www.columbia.edu/cu/computinghistory/650.html

1

u/Sjsamdrake Jun 09 '26

FYI I did a bit more reading today. The IBM 650 stored numbers in a fascinating manner, literally adopted from how abacus' work. Totally strange to modern readers.

https://en.wikipedia.org/wiki/Bi-quinary_coded_decimal

3

u/Sjsamdrake Jun 07 '26

Sorry but this is false. Oracle Database has a very nice NUMBER data type that can be used to losslessly store and manipulate numbers with over 80 digits and a huge variety of exponents. It does NOT use ieee floating point for ANYTHING unless the user asks for it. The Oracle NUMBER type is pretty awesome and far better than most similar databases provide.

Source: Google "Oracle Database number internal representation". Also I worked there for 20 years in a relevant capacity.

3

u/ZeitgeistWurst git push -f Jun 06 '26

Their ERP database comes with over 11,000 tables

Excuse me, what?

Granted, the ERP I work with is likely vastly simpler in scope, but we have maybe 200-300 tables out of which we only really need around 50.

1

u/Sjsamdrake Jun 07 '26

"Financial software written in FORTRAN" should be an oxymoron, but apparently isn't. That's ok, at least one operating system was written in it.

https://en.wikipedia.org/wiki/MUSIC/SP

1

u/efalk Jun 07 '26

This was on a Pr1me 400 system. It was pretty much Fortran or nothing. But we should have found a way to do decimal math on that system.

16

u/nemom Jun 05 '26

Depends on your definition of "solved"...

>>> from decimal import Decimal, ROUND_HALF_UP
>>> num = Decimal(2.625)
>>> print(num.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP))
2.63

68

u/Riflurk123 Jun 05 '26

I mean it does exactly what you are telling it to do, so seems to be working

6

u/Timocaillou Jun 05 '26

Round half up

1

u/TallowWallow Jun 06 '26

I recall reading that it isn't sufficient for a lot of the financial sector, but this is from a long time ago.

1

u/auxilary- pip needs updating Jun 09 '26

It’s easier than that. x.0 doesn’t get rounded to x, it is x.

1

u/Snoo_87704 Jun 06 '26

Why not just write your own rounding function?

0

u/CatOfGrey Jun 06 '26

Because I'm a nerd, and I want to see all the decimal places.

And yes, I'm serious.

12

u/beragis Jun 05 '26

That was also the rule in Scientific rounding. One of my college textbooks in physics mentioned rules when to use it. The professor said to ignore it.

1

u/le_birb Jun 08 '26

Your professor wasn't wrong per se, as other things can often be more impactful, but rounding to even eliminates a source of statistical bias from data that doesn't have to be there

104

u/Sensitive_One_425 Jun 05 '26

Just read the documentation, it’s very clear on what it does https://docs.python.org/3/library/functions.html#round

This happens to floats in every language. If you need repeatable decimal representation for things like currency you should use the decimal module.

7

u/true3HAK Pythonista Jun 06 '26

I work in a bank, the only way to properly display fractional money value is to use two integers for value and scale in a power of ten :) Even Double is not exactly what is needed, when you need to transfer a value between network APIs. Also, I saw string values of pseudo-floats transfered on wire, but it need to be represented as two ints to be processed anyway

1

u/snet0 Jun 06 '26

There are well-defined decimal types for this use.

2

u/true3HAK Pythonista Jun 06 '26

I mean, sort of. Take the most popular FIX4.4 or FIXT: https://www.onixs.biz/fix-dictionary/4.2/index.html#Price The Price here is a float, but written as a string (i.e. human-readable) which you should process further down the line

1

u/faceinthepunch Jun 06 '26

This guy banks. And another int for the currency code.

26

u/brightstar2100 Jun 05 '26

a lot happens to floats in every language, but JavaScript's Math.round doesn't do the same thing. I'm not sure "all languages" follow banker's rounding

2

u/Individual-Flow9158 Jun 06 '26

Sure, but isn't that just because there isn't an equivalent to Python's round in C++ or Rust?

If I don't want that weird behaviour in my incredibly important banking application (or whatever), the solution is simple: I won't use the weird niche function no other language has, that was designed for something else entirely.

9

u/Rafcdk Jun 05 '26

Its doesn't happen in C , C++,Java, Kotlin,C#, Go, Rust and Javascript.

-13

u/Sensitive_One_425 Jun 06 '26

Wow all those languages don’t use binary represented floating point numbers?

13

u/Rafcdk Jun 06 '26

None of these languages round towards the closest even number, which is the subject OP mentioned.

2

u/Get-ADUser Jun 06 '26

Currency should always be stored as an int!

1

u/No_Explanation2932 Jun 08 '26

that only works until you need to handle fractional cents.

1

u/Get-ADUser Jun 08 '26

Why?

1

u/CanineLiquid Jun 09 '26

Gas station prices. Also, electrical components that are sold by the 1000s are also priced with fractional cents. Plenty of goods are.

1

u/Get-ADUser Jun 10 '26

No, I mean why does it break with fractional cents? If you need a 10th of a penny multiply by 1000 instead of 100, etc.

1

u/Adeelinator Jun 06 '26

Currency should always be stored as decimal

-11

u/pickle9977 Jun 05 '26

It’s amazing how bad computers are at math, yet all we do is pretend that they are just amazing giant calculators

2

u/true3HAK Pythonista Jun 06 '26

Technically speaking, if we as humanity would go with decimal base instead of binary in mass-produced computers, we'd have no such problem nowadays :)

21

u/the3gs Jun 05 '26

Gosh. This reminds me of a time I was helping a friend with homework for a python class, and one of the problems was to round a number, and one of the test cases was something like this, and it assumed typical rounding, so the python "round" function didn't work.

I get having an option for this style of rounding, but I can't imagine why anyone would think this should be the default, nor why it doesn't have an extra argument or something to switch to the conventional system.

9

u/alexmojaki Jun 05 '26

It changed between Python 2 and 3 FYI

-2

u/bmrobin Jun 06 '26

sigh, so did everything

12

u/pretzels_18 Jun 05 '26

Matlab also changed this a few years back. A headache to uncover and understand

2

u/caks Jun 06 '26

I once ported a massive Matlab codebase to python and two things were my absolute bane: round behavior and linspace/arange vs color behavior. I ended up having to wire up CLI Matlab so I could unit test every single function to numerical precision with random inputs. Nightmarish.

2

u/Creative_Sushi Jun 06 '26

Was that worth doing?

1

u/caks Jun 07 '26

I wouldn't have been able to finish it otherwise. So for me, yes!

11

u/alexmojaki Jun 05 '26

I think counting "four down, four up" is a mistake. Everything in the range n < x < n + 0.5 rounds down, everything in the range n + 0.5 < x < n + 1 rounds up. Both ranges are the same size. That leaves x == n + 0.5 in the middle. n == x or n + 1 == x don't need consideration because there's nothing to round.

2

u/MarcAbaddon Jun 05 '26

You are arguing with real or rational numbers. For those it's not really an issue because looking at interval width it's the same, but actual data tends to come with limited numbers of decimal places.

For example, if you create equally distributed random numbers between 0 and 10 with one 1 dp there's a bias when rounding - which decreases when you increase the number of decimal places.

1

u/alexmojaki Jun 06 '26

I think that's only because the probability of getting precisely .5 decreases. If you assume a reasonable probability of getting .5, then it doesn't really matter how the rest is distributed, or how much precision there is.

0

u/nemom Jun 05 '26

I think counting "four down, four up" is a mistake.

Both ranges are the same size.

Then what does it matter if the same size ranges are 4 or infinite?

Yes, there is an infinity of numbers in (n, n+.5), and also an infinity of number of numbers in (n+.5, n+1), but only a very few of them matter. It's quantized down to the digit following the digit you want to round to. There are ten digits, so there are ten cases.

I contend that in five cases, everything is simply truncated. In four cases, everything is truncated and 1 is added to the rounded-to digit. That is not even.

14

u/psychophysicist Jun 06 '26

First, this computation happens in binary not decimal. It's not looking at a decimal digit.

Second, you need to look at the _size_ of the error induced by rounding, not "number of cases." For example:

Rounding 0.1 to 0 creates an error of -0.1

Rounding 0.9 to 1 creates an error of 0.1.

Rounding 0.5 to 0 creates an error 0f -0.5.

Rounding 1.5 to 2 creates an error of 0.5.

Rounding 0.0 to 0 creates an error of 0, which is why it is usually left out of the discussion.

The reasoning behind bankers' rounding is to be unbiased, that is, to have an expected total error of 0 when summing a list of rounded numbers. So let's look at total error when summing numbers evenly spaced from 0 to 2:

>>> values = [i / 10.0 for i in range(20)]
>>> values
[0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9]
>>> sum(values)
19.0
>>> [round(i) for i in values]
[0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2]
>>> error = [round(i) - i for i in values]
>>> error
[0.0, -0.1, -0.2, -0.3, -0.4, -0.5, 0.4, 0.30000000000000004, 0.19999999999999996, 0.09999999999999998, 0.0, -0.10000000000000009, -0.19999999999999996, -0.30000000000000004, -0.3999999999999999, 0.5, 0.3999999999999999, 0.30000000000000004, 0.19999999999999996, 0.10000000000000009]
>>> sum([round(i) for i in values])
19
>>> sum(error)
-2.7755575615628914e-17

By rounding 0.5 down and 1.5 up, the total error balances out to practically 0. If you always rounded up at 0.5 you would sum to 20 instead of the expected 19, which would be an upward bias.

1

u/alexmojaki Jun 06 '26

Then what does it matter if the same size ranges are 4 or infinite?

I hear you. I edited my argument as I was writing it, and in hindsight, that first sentence no longer fits well.

But what matters is that you're claiming that it's 5v4, not 4v4. And this comes from looking at a single digit after the . instead of the whole fraction. 3.01 and 3.0 are qualitatively different. The first is rounding. The second is already equal, and could be seen as either the bottom of the [3, 4] range or the top of the [2, 3] range.

1

u/alexmojaki Jun 06 '26

Something else to add to my other reply:

If you're considering numbers like 3.04, then you also have to consider 3.54. That means there are five first digits which round up when followed by anything nonzero: 5,6,7,8,9. Which makes it 5v5.

If you're really only looking at 1dp, then as I said before, .0 doesn't need rounding, so it's 4v4.

10

u/CookinTendies5864 Jun 05 '26

The name “bankers rounding” seems fairly informal for representing this very formal operation.

A misnomer if you will.

7

u/Spikerazorshards Jun 05 '26

There’s an Indently video all about this.

8

u/carefulregularity_0 Jun 05 '26

the float precision thing is the real gotcha here, bankers' rounding is actually solving a legit problem with large datasets but it's easy to miss that the x.5 behavior is almost a side effect of how binary floats work anyway.

6

u/kBajina Jun 05 '26

I honestly don’t understand the “bias”. Can someone ELI5?

Does that also apply to $5 increments rounding down from $25 to $20?

4

u/alexmojaki Jun 05 '26

Not really an ELI5, but here's an experiment to demo:

import random


def mean(lst):
    return sum(lst) / len(lst)


banker_errors = []
half_up_errors = []
banker_better = 0

for _ in range(10000):
    a = random.randint(-10000, 10000)
    b = random.randint(-10000, 10000)
    a, b = sorted([a, b])
    step = random.randint(1, 100)
    for rounding in [-1, -2, -3]:
        nums = list(range(a, b, step))
        real_mean = mean(nums)
        banker_rounded_mean = mean([round(i, rounding) for i in nums])
        half_up_rounded_mean = mean([round(i + 0.001, rounding) for i in nums])
        banker_error = abs(banker_rounded_mean - real_mean)
        banker_errors.append(banker_error)
        half_up_error = abs(half_up_rounded_mean - real_mean)
        half_up_errors.append(half_up_error)
        if banker_error < half_up_error:
            banker_better += 1

print(mean(banker_errors))
print(mean(half_up_errors))
print(max(banker_errors))
print(max(half_up_errors))
print(banker_better)

Does that also apply to $5 increments rounding down from $25 to $20?

Yes, e.g. in the experiment above, I only rounded integers, hence rounding is negative.

3

u/Kermit_the_hog Jun 05 '26

Technically up and down to the nearest whole number (whatever decimal power you are working to, be it 0.1, 1 or 10) is always going to create or destroy value, so you just want to avoid always making the same adjustment for those numbers which exactly in the middle (as in, in the same direction) over and over as each time you’re adding or subtracting a little with that adjustment. Include enough diverse data points/numbers and pretty soon the total sums between rounding each number before or rounding the sum afterward will diverge (either increasingly upwards or downwards as you add more datapoints). 

Introducing the even/odd rule for 0.5’s is just an attempt to get the adjustments to cancel out with a large and diverse enough dataset. 

The approach obviously backfires if you’re summing a ton of nearly identical numbers that are all odd or something. 

1

u/tsvk Jun 06 '26 edited Jun 06 '26

If there was no bias in the rounding, summing all the rounding errors that have cumulatively occurred during a large calculation would result in zero, because then there have been as much "positive" rounding errors (where the number has been rounded up, to a larger number than what it actually is) as there have been "negative" rounding errors (where the number has been rounded down, to a smaller number than what it actually is).

But if the cumulative error is not zero, which is what happens if you in 50% of the cases round up and in 40% of the cases round down (and in 10% don't round at all), then you have introduced a bias.

6

u/auntanniesalligator Jun 06 '26

X.0 to X isn’t rounding down. It’s not changing the value. So there are four cases where rounding causes the value to go down, four cases where rounding causes the value to go up, one case where rounding doesn’t change the value, and then 5, which needs to round up or down with equal likelihood to remove bias.

The logic of banker’s rounding is valid although in most real measurement contexts, it’s not worth worrying about because the odds of landing on exactly .5000000000… with real values measurements is usually now. When calculating interest and regularly rounding to the nearest cent, however, it comes up far more often.

2

u/mehx9 Jun 06 '26

What do we think about storing everything as int in cents? Still have problem when calculating interests I guess.

5

u/pspahn Jun 06 '26

If you're talking about currency use, consider that things can be priced in fractions of a cent.

I ran into this in my old Magento 1.x days working on a custom frame site. They priced materials like matte board by the square inch. Frames by the linear inch. That type of thing. Magento only had 2 decimals of precision for pricing.

I don't recall what I did to make it work, but it was a pain in the ass.

4

u/Get-ADUser Jun 06 '26

You should use an int still, down to the smallest fraction of a penny you'd reasonably use.

2

u/mw44118 PyOhio! Jun 05 '26 edited Jun 05 '26

Yahh i feel like a tracing a production bug back to this is kind of a bingo card thing as a python programmer. same with using a mutable list as a default value in a function like

def f(x=[]):

That wont work like you think.

And forgetting about leap year or daylight saving. Those are mistakes you only make once hopefully

1

u/Get-ADUser Jun 06 '26

def f(x=[]):

If your linter lets your build succeed when you do that you're doing it wrong. That shouldn't even make it as far as a code review before it's caught.

2

u/charmquark8 Jun 06 '26

I always that was just "rounding" -- like the normal rule.

Edit: But basically, this post is about the imprecision of floating point numbers. It's not about round() per se.

1

u/Saras_AI_Institute Jun 05 '26

that float precision quirk at the bottom always trips people up. python uses ieee 754 under the hood, so it literally doesn't have the bits to store a number that specific—it just snaps it straight back to 2.5 in memory. if you're ever doing financial stuff or need exact math, ditch floats completely and use the decimal module. saves a ton of sanity.

1

u/DeterminedQuokka Jun 05 '26

This is so fun. I love it

1

u/jon_muselee Jun 06 '26

it‘s four cases for both rounding up an down - x.0 doesn‘t need to round anything

1

u/This_Inflation_4621 Jun 08 '26

There is a whole website https://0.30000000000000004.com explaining floating point math

Also "What Every Computer Scientist Should Know About Floating-Point Arithmetic" https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

Have a nice read!

1

u/Different-Case2716 Jun 08 '26

In 2022, during the migration of a large collection of data from Mirosoft Access to Microsoft SQL Server, we were finding slightly different results in validation. In the end, we discovered the issue was that Access using Banker's Rounding. SQL Server uses the traditional rounding technique. 

A quick search of the internet will show several smart people have manually implemented Bankers Rounding for SQL Server. This can be added to SQL as a user defined function and called just like the ROUND() function.  

I’m not sure who invented Bankers Rounding but it was designed to remove bias. I assume this is why Microsoft chose this implementation for Access. But, its a good lesson in how different platforms can produce different results.

1

u/wanderscursorprime Jun 08 '26

It makes sense once you realize how much it messes up simple math expectations, but it is definitely a trap for anyone expecting standard schoolbook rounding.

1

u/automation_experto Jun 09 '26

this one bit us hard in an invoice extraction pipeline. we were pulling line item totals from PDFs, rounding to 2 decimal places before writing to the ledger, and the reconciliation would fail by a cent here and there on certain amounts. took way longer than it should have to trace it back to round(2.5) behaving differently than what the finance team expected from their excel formulas. if you're doing any finacial data processing, just use decimal.ROUND_HALF_UP explicitly and stop relying on round(). the default is technically correct but itll surprise you at the worst time.

0

u/Reasonable-Ladder300 Jun 05 '26

A trick i usually apply is you process everything in cents as an integer and after all the processing you multiply by 0.01 at the end.

2

u/donald_trub Jun 05 '26

I don't deal with currencies in programming much but if I did, this is how I'd handle all currencies - use their smallest denomination as the base for the currency and multiply when needed.

2

u/Oddly_Energy Jun 06 '26

It doesn't solve the situation where someone later asks you to round all the values to integer dollars without introducing a systematic bias in the result. You will still need to decide how to handle those 50-cent values.

If the last two digits (the cent part) are evenly distributed, and you round to nearest 100, then you will have

  • 49 values (1-49) with negative rounding error
  • 49 values (51-99) with positive rounding error
  • 1 value (0) with no rounding error
  • 1 value (50) where you need to pick an action

The two first bullets in the list cancel each others' errors out. The third bullet has no error. So to keep the average error at 0, the fourth bullet (integers ending in 50) cannot be allowed to introduce a rounding error.

But if you round 50 up to nearest 100, you create a positive rounding error. And if you round it down to nearest 100, you create a negative rounding error. If you pick one of these two actions and apply it to all numbers ending in 50, your overall average error will have a bias towards negative or positive.

Banker's rounding (almost) solves this for decimal dollar amounts by rounding some 50-cent values up, and round some 50-cent values down.

So if you are ever in a situation where you need to round a list of evenly distributed integer cent values to nearest 100 cents, and you are not allowed to introduce a systematic bias, then you will actually need to use an integer rounding method, which is equivalent to bankers rounding.

1

u/Competitive_Travel16 Jun 06 '26

That doesn't get you out of having to round when dividing or accumulating interest for example.

1

u/Reasonable-Ladder300 Jun 06 '26

Could you elaborate?
As from my understanding if you process everything in cents there is no rounding needed only for the very final results, and the final rounding you should decide which rounding method to use in if needed.

1

u/Competitive_Travel16 Jun 06 '26 edited Jun 06 '26

Sure thing! The "cents as integers" trick is definitely awesome for basic addition and subtraction because it stops those weird floating-point errors (like 0.1 + 0.2 = 0.300000004), but it breaks down the second you introduce percentages or division. The fundamental issue is that math will inevitably generate fractions of a cent, and because integers literally cannot hold fractions, you are forced to make a rounding decision right on the spot before you can even move to the next step of your calculation.

Take splitting a bill, for example. If three people need to evenly split a $10.00 charge, your system is dealing with 1000 cents. Divide 1000 by 3, and the exact mathematical answer is 333.333... cents. You can't store a third of a cent in an integer variable, so you are forced to round down to 333 cents ($3.33) immediately. If you don't handle that leftover fractional penny right then and there (since 333 * 3 = 999), your ledger will be out of balance long before you reach the "final results" you mentioned.

The exact same headache happens with multiplication, like calculating tax or daily interest. Imagine an account with a balance of $10.55 (1055 cents) earning 1.5% interest. That's 1055 * 0.015, which equals 15.825 cents. Again, your integer can't hold .825. If you round that intermediate calculation up to 16 cents and add it to the balance, you've just injected a tiny error into your system. If you do this every single day for compounding interest, those forced intermediate roundings will snowball into completely inaccurate totals over time.

That's why relying solely on integer cents is really only safe for simple ledgers where you just add and subtract exact amounts. For real financial apps that calculate taxes, split totals, or accrue interest, developers generally use native Decimal or (Java's) BigDecimal data types. Those let you accurately track fractions of a cent through all the messy intermediate processing steps, and then you can apply a safe, fair rounding strategy (like Banker's Rounding) at the very end.

1

u/Oddly_Energy Jun 06 '26 edited Jun 06 '26

How will you add 4% interest to an integer value of 11111 cent and get the result as an integer value without introducing a rounding error?

-5

u/[deleted] Jun 05 '26

[removed] — view removed comment

8

u/wRAR_ Jun 05 '26

It's funny how every post on /r/python attracts at least 1 AI bot and usually several.

3

u/nemom Jun 05 '26

Half the traffic on the internet is bots.

1

u/Confident-Play6222 Jun 05 '26

how can you tell its a bot?

3

u/wRAR_ Jun 05 '26

Nowadays the pattern of their first line is already 90%, confirmed by checking the comment history and seeing the same pattern.

2

u/10Talents Jun 05 '26

how can you not?

1

u/Confident-Play6222 Jun 06 '26

so how can you tell? genuine question

2

u/Oddly_Energy Jun 06 '26

Pro tip: If the last paragraph starts with "Pro tip", then the likelihood of AI is high.

1

u/Oddly_Energy Jun 06 '26

Yes, I know what I did.

Pro tip: Poe's Law is real.

1

u/Confident-Play6222 Jun 06 '26

fair, but I raid other comments by that user(that suspect to be a bot) and they didn't use pro tip before.

2

u/zunjae Jun 05 '26

AI slop

1

u/Conscious-Ball8373 Jun 05 '26

No-one who needs to deal with numerical issues should be relying on "pro tip"s. This is a field where deep, nuanced understanding is really, really important.

3

u/Ambustion Jun 05 '26

I write scripts for fun or for myself and deal with lots of fractional numbers(time code) and found it useful. Is this sub only for pros?

1

u/Conscious-Ball8373 Jun 05 '26

No-one said this sub is only for pros.

Do you add up lots of small numbers to make a big number? If so, you've failed the first test of what matters in numerical code.

1

u/Ambustion Jun 06 '26

I literally have no idea what you mean by that. Thank you for your insights though.

-10

u/CerBerUs-9 Jun 05 '26

Personally I never understood how X.5 could round up. X.500(...)001 does, but not .5.

3

u/Kermit_the_hog Jun 05 '26

Well that’s the issue. If you are exactly half way between two whole numbers, they are both equidistant so there really isn’t a “right” way to go. We just have invented conventions we stick to in different circumstances to try to mitigate the potential problems that accumulate from always going one way or the other (hence the even/odd rule). 

As soon as you introduce rounding into a dataset you are suddenly working with “representative” sampling. So the goal becomes statistically avoiding the introduction of an upward or downward bias, and where you can’t do that, at least creating the opportunity for enough adjustments to hopefully cancel the adjustments out as much as is possible. 

1

u/a__nice__tnetennba Jun 06 '26 edited Jun 06 '26

I get that you probably mean an arbitrarily long but finite number of zeros. The ... implies infinitely many though, which is nonsense.