Welcome to DU! The truly grassroots left-of-center political community where regular people, not algorithms, drive the discussions and set the standards. Join the community: Create a free account Support DU (and get rid of ads!): Become a Star Member Latest Breaking News Editorials & Other Articles General Discussion The DU Lounge All Forums Issue Forums Culture Forums Alliance Forums Region Forums Support Forums Help & Search

Bernardo de La Paz

(56,409 posts)
Mon May 26, 2025, 12:06 AM May 26

The Joys of Being Wrong, the diligence of DUer "SunSeeker", and the Elimination of the Penny

This the full nerd report. My brief is in General Discussion. Python program in Post #1 here.

Long story short: I made an assumption that was wrong, but then I found that I was kind of right in one way but the situation is a bit mixed.

Long story short on Elimination of Penny: Using Lombra distribution of prices, there is a cost to the poor of about 3 tenths of a cent per cash transaction if there is not a sales tax, but no cost if there is sales tax.

The joy is learning that I was wrong. Now I know better. I was however right in how it works in Canada based on grocery bills because they almost always have at least one taxable item (food not generally taxable).

There was a big discussion in GD about Eliminating the Penny. There was a lot of back and forth about greed and history and ways of doing it and fairness. I made an assumption to think mathematically about it and made arguments for fairness based (implicitly) on math. My assumption was wrong.

This came to my attention from a paper quoted by SunSeeker, who had done more due diligence in the thread than all of the rest of us put together. Hat tip to SunSeeker

In the paper by Shapiro 2017 it references a paper by Lombra from 2001, which I had to really dig to find. Reading Lombra's paper I suddenly realized my folly. I had assumed random prices. I should have seen the obvious: a lot of prices end in 99 cents. There was veiled reference to a counter study with regard to sales taxes. Lombra rebutted it in 2007. His rebuttal seemed weak. Then I found the counter by Warples in 2007 considering the effect of sales taxes that Lombra was rebutting. Shapiro seemed to be ignoring Warples.

I wondered if the studies were done on slow old machines because 5,000 transactions seemed pitifully small for a simulation. I thought, "I can whip up a Python program lickety-split". Thus I embarked on hours long nerdiness tuning a program I wrote.

I ended up doing 16 simulations with over three hundred million transactions simulated. They were in four groups of four, each being about 20 million transactions. I simulated prices randomly in the range of 20 cents to 9.99, but I made the tail digit distributions fall into four classes. Lombra had 3,585 prices, I had 4,000. In each group I ran with no tax, medium tax, high tax and random tax in the range to the high tax. (Random taxes simulates a nationwide range.)

A summary of the summary: Rounding prices is the fairest, but not the most realistic policy. Random price tails is as expected even (fair) but does not exist. Realistic price distributions, either Lambro or another reasonable one are unfair by about 3 cents per transaction in the absence of taxes, but taxes make the rounding fair (not to forget that sales taxes are regressive). The level of taxation (when not zero) did not affect any scenario differently.

Here is the summary:

Simulation 1: Prices rounded to 0 or 5 cents
per transaction: 0.000 cents.

Simulation 2: Prices rounded to 0 or 5 cents with tax rate 5.0 %
per transaction: -0.002 cents.

Simulation 3: Prices rounded to 0 or 5 cents with tax rate 11.25 %
per transaction: 0.001 cents.

Simulation 4: Prices rounded to 0 or 5 cents with random tax rates
per transaction: -0.009 cents.

Simulation 5: Prices random tails
per transaction: -0.000 cents.

Simulation 6: Prices random tails with tax rate 5.0 %
per transaction: -0.006 cents.

Simulation 7: Prices random tails with tax rate 11.25 %
per transaction: -0.016 cents.

Simulation 8: Prices random tails with random tax rates
per transaction: 0.001 cents.

Simulation 9: Lombra distribution of prices
Percent last digit:
0: 0.2 %
1: 0.1 %
2: 0.2 %
3: 0.5 %
4: 9.0 %
5: 3.7 %
6: 2.9 %
7: 0.1 %
8: 0.8 %
9: 82.5 %

per transaction: 0.339 cents.

Simulation 10: Lombra distribution of prices with tax rate 5.0 %
per transaction: -0.014 cents.

Simulation 11: Lombra distribution of prices with tax rate 11.25 %
per transaction: -0.002 cents.

Simulation 12: Lombra distribution of prices with random tax rates
per transaction: -0.002 cents.

Simulation 13: Imagined distribution of prices
Percent last digit:
0: 1.6 %
1: 0.1 %
2: 0.1 %
3: 0.1 %
4: 2.0 %
5: 6.0 %
6: 0.1 %
7: 2.0 %
8: 8.0 %
9: 80.0 %
per transaction: 0.295 cents.

Simulation 14: Imagined distribution of prices with tax rate 5.0 %
per transaction: -0.007 cents.

Simulation 15: Imagined distribution of prices with tax rate 11.25 %
per transaction: -0.002 cents.

Simulation 16: Imagined distribution of prices with random tax rates
per transaction: -0.002 cents.


Here are the final results of each of the 16 simulation runs in more detail (but edited to eliminate lots more detail):

TransactRound v0.3
python 3.12.6

Simulation of Rounding on large numbers of Transactions
by a method like Lombra, Eastern Economic Journal, v27n4, Fall 2001

Size of prices 4000

Simulation 1: Prices rounded to 0 or 5 cents
Count 20,000,000: Diffs counts: -2: 0.0 %, -1: 0.0 %, 0:100.0 %, 1: 0.0 %, 2: 0.0 %.
Total_diffs 0: per transaction: 0.000 cents.


Simulation 2: Prices rounded to 0 or 5 cents with tax rate 5.0 %
Count 20,000,000: Diffs counts: -2: 20.2 %, -1: 20.0 %, 0: 19.7 %, 1: 20.2 %, 2: 20.0 %.
Total_diffs -34,239: per transaction: -0.002 cents.

Simulation 3: Prices rounded to 0 or 5 cents with tax rate 11.25 %
Count 20,000,000: Diffs counts: -2: 19.8 %, -1: 20.0 %, 0: 20.2 %, 1: 20.2 %, 2: 19.8 %.
Total_diffs 12,537: per transaction: 0.001 cents.

Simulation 4: Prices rounded to 0 or 5 cents with random tax rates
Count 20,000,000: Diffs counts: -2: 19.9 %, -1: 19.7 %, 0: 21.3 %, 1: 19.4 %, 2: 19.6 %.
Total_diffs -182,473: per transaction: -0.009 cents.

Simulation 5: Prices random tails
Count 20,000,000: Diffs counts: -2: 20.0 %, -1: 20.0 %, 0: 20.0 %, 1: 20.0 %, 2: 20.0 %.
Total_diffs -532: per transaction: -0.000 cents.

Simulation 6: Prices random tails with tax rate 5.0 %
Count 20,000,000: Diffs counts: -2: 20.1 %, -1: 20.1 %, 0: 19.9 %, 1: 20.1 %, 2: 19.8 %.
Total_diffs -123,521: per transaction: -0.006 cents.

Simulation 7: Prices random tails with tax rate 11.25 %
Count 20,000,000: Diffs counts: -2: 20.3 %, -1: 20.2 %, 0: 20.2 %, 1: 19.6 %, 2: 19.8 %.
Total_diffs -323,513: per transaction: -0.016 cents.

Simulation 8: Prices random tails with random tax rates
Count 20,000,000: Diffs counts: -2: 20.0 %, -1: 20.0 %, 0: 20.0 %, 1: 20.0 %, 2: 20.0 %.
Total_diffs 11,021: per transaction: 0.001 cents.

Simulation 9: Lombra distribution of prices
Percent last digit:
0: 0.2 %
1: 0.1 %
2: 0.2 %
3: 0.5 %
4: 9.0 %
5: 3.7 %
6: 2.9 %
7: 0.1 %
8: 0.8 %
9: 82.5 %
Count 20,000,000: Diffs counts: -2: 19.2 %, -1: 13.8 %, 0: 10.2 %, 1: 27.5 %, 2: 29.3 %.
Total_diffs 6,770,142: per transaction: 0.339 cents.

Simulation 10: Lombra distribution of prices with tax rate 5.0 %
Count 20,000,000: Diffs counts: -2: 20.5 %, -1: 20.2 %, 0: 19.6 %, 1: 19.8 %, 2: 19.9 %.
Total_diffs -285,468: per transaction: -0.014 cents.

Simulation 11: Lombra distribution of prices with tax rate 11.25 %
Count 20,000,000: Diffs counts: -2: 20.2 %, -1: 19.8 %, 0: 20.0 %, 1: 20.1 %, 2: 19.9 %.
Total_diffs -38,637: per transaction: -0.002 cents.

Simulation 12: Lombra distribution of prices with random tax rates
Count 20,000,000: Diffs counts: -2: 19.9 %, -1: 20.3 %, 0: 20.0 %, 1: 19.9 %, 2: 20.0 %.
Total_diffs -36,231: per transaction: -0.002 cents.

Simulation 13: Imagined distribution of prices
Percent last digit:
0: 1.6 %
1: 0.1 %
2: 0.1 %
3: 0.1 %
4: 2.0 %
5: 6.0 %
6: 0.1 %
7: 2.0 %
8: 8.0 %
9: 80.0 %
Count 20,000,000: Diffs counts: -2: 19.5 %, -1: 14.2 %, 0: 10.9 %, 1: 28.1 %, 2: 27.3 %.
Total_diffs 5,895,016: per transaction: 0.295 cents.

Simulation 14: Imagined distribution of prices with tax rate 5.0 %
Count 20,000,000: Diffs counts: -2: 20.2 %, -1: 20.1 %, 0: 19.7 %, 1: 19.8 %, 2: 20.1 %.
Total_diffs -133,280: per transaction: -0.007 cents.

Simulation 15: Imagined distribution of prices with tax rate 11.25 %
Count 20,000,000: Diffs counts: -2: 19.8 %, -1: 20.2 %, 0: 20.3 %, 1: 19.9 %, 2: 19.8 %.
Total_diffs -41,194: per transaction: -0.002 cents.

Simulation 16: Imagined distribution of prices with random tax rates
Count 20,000,000: Diffs counts: -2: 19.9 %, -1: 20.2 %, 0: 20.1 %, 1: 19.9 %, 2: 19.9 %.
Total_diffs -32,305: per transaction: -0.002 cents.

Fini.

4 replies = new reply since forum marked as read
Highlight: NoneDon't highlight anything 5 newestHighlight 5 most recent replies
The Joys of Being Wrong, the diligence of DUer "SunSeeker", and the Elimination of the Penny (Original Post) Bernardo de La Paz May 26 OP
Python program Bernardo de La Paz May 26 #1
Won't it just take us back to how things were when the penny had the buying power of todays nickel? Blues Heron May 26 #2
US got rid of the half penny in the mid 1800s. Canada got rid of the penny around 2013. . . nt Bernardo de La Paz May 26 #3
Australia doesn't use a penny tikka May 26 #4

Bernardo de La Paz

(56,409 posts)
1. Python program
Mon May 26, 2025, 12:12 AM
May 26

from math import floor
from random import random
import sys

version = "v0.3"
print (f'TransactRound {version}')
print (f'python {sys.version [0: 7]}n')

prices = []
price_counts = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] # 10 tails
coi = [0, 0, 0, 0, 0]
diffs_count = [0, 0, 0, 0, 0]
total_diffs = 0
sim_num = 0
numbers_of_items = [1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5]



def price_range (tail, count) :
for _ in range (count):
price = rand_price (tail)
prices.append (price)
price_counts [tail] += 1



def rand_price (tail:int) -> float:
while True:
price = floor (random () * 99) * 10 + tail
if price >= 20:
return price



def print_results (num:int) :
print (f'nCount {num: 9, } :',
f'1 item: {100*(coi[0]/num):4.1f}%, ',
f'2: {100*(coi[1]/num):4.1f}%, ',
f'3: {100*(coi[2]/num):4.1f}%, ',
f'4: {100*(coi[3]/num):4.1f}%, ',
f'5: {100*(coi[4]/num):4.1f}%.')
print (f'Diffs counts: -2:{(diffs_count [0]/num)*100:5.1f} %, ',
f'-1:{(diffs_count [1]/num)*100:5.1f} %, ',
f'0:{(diffs_count [2]/num)*100:5.1f} %, ',
f'1:{(diffs_count [3]/num)*100:5.1f} %, ',
f'2:{(diffs_count [4]/num)*100:5.1f} %.')
print (f'Total_diffs {total_diffs:,}:',
f' per transaction: {total_diffs / num:5.3f} cents.')



def simulate (count:int, title:str, tax_percent:float=0.0, pr_sample:bool=False ):
'''
tax_rate 0.0 is silent.
tax_rate < 0.0 means use random tax rates
'''
global coi, diffs_count
coi = [0, 0, 0, 0, 0]
diffs_count = [0, 0, 0, 0, 0]

if tax_percent == 0.0:
taxing:bool = False
else:
taxing:bool = True
if tax_percent < 0.0:
tax_rate_random = True
else:
tax_rate = tax_percent / 100
tax_rate_random = False

global sim_num
sim_num += 1

title = f'Simulation {sim_num}: {title}'
if taxing:
if tax_rate_random:
title += ' with random tax rates'
else:
title += f' with tax rate {tax_percent} %'
print (f'nn{title}n')

if pr_sample or ((sim_num % 4) == 1):
out = f'Percent last digit:n'
for i in range (10):
out += f'{i}: {price_counts /40:4.1f} %n'
print (out)

diff_round = [0, -1, -2, 2, 1]
if pr_sample:
print ('First 20 transactions:n')
item_str = ''

global total_diffs
total_diffs = 0
for i in range (count):
num_items = numbers_of_items [int (random () * 20)]
coi [num_items - 1] += 1
total = 0
for j in range (num_items):
index = floor (random () * 3998.9)
item = prices [index]
if i < 20:
item_str += f' {item/100:5.2f}'
total += item

if taxing:
if tax_rate_random:
tax_rate = 0.0025 * int (46 * random ())
tax = round (total * tax_rate)
total_w_tax = total + tax
tax_str = f' tax rate {tax_rate*100:4.1f}%,'
tax_str += f' tax {tax/100:5.2f}, total w tax {total_w_tax/100:5.2f},'

else:
total_w_tax = total
tax_str = ""

digit = total_w_tax % 5
diff = diff_round [digit]
diffs_count [2 + diff] += 1
cash = total_w_tax + diff
total_diffs += diff
if pr_sample and (i < 20):
out = f'items {num_items}: '
out += f' total {total/100:5.2f},'
out += tax_str
out += f' diff {diff:2},'
out += f' cash {cash/100:5.2f}:'
out += f' {item_str}'
print (f'{(i+1):2}: {out}')
item_str = ''
condition = False
else:
condition = (i == count / 1000) or (i == count / 100) or
(i == count / 10)
if condition:
print_results (i)

print_results (count)
print (f'nEnd of {title}.')



count = 20 * 1000 * 1000
count = 200 * 1000

prices = []
price_counts = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] # 10 tails

price_range (0, 400)
price_range (5, 3600)

print ('Simulation of Rounding on large numbers of Transactions')
print (' by a method like Lombra, Easter Economic Journal, v27n4, Fall 2001n')

print (f'Size of prices {len (prices)}')

simulate (count, "Prices rounded to 0 or 5 cents" )
simulate (count, "Prices rounded to 0 or 5 cents", 5.0)
simulate (count, "Prices rounded to 0 or 5 cents", 11.25)
simulate (count, "Prices rounded to 0 or 5 cents", -99)

prices = []
price_counts = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] # 10 tails

price_range (0, 400); price_range (1, 400); price_range (2, 400);
price_range (3, 400); price_range (4, 400); price_range (5, 400);
price_range (6, 400); price_range (7, 400); price_range (8, 400);
price_range (9, 400)

simulate (count, "Prices random tails" )
simulate (count, "Prices random tails", 5.0)
simulate (count, "Prices random tails", 11.25)
simulate (count, "Prices random tails", -99)

prices = []
price_counts = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] # 10 tails

# 4 * percent * 10. Lombra data.
price_range (0, 4 * 2); price_range (1, 4 * 1); price_range (2, 4 * 2)
price_range (3, 4 * 5); price_range (4, 4 * 90); price_range (5, 4 * 37)
price_range (6, 4 * 29); price_range (7, 4 * 1); price_range (8, 4 * 8)
price_range (9, 4 * 825)

simulate (count, "Lombra distribution of prices", 0.0, True)
simulate (count, "Lombra distribution of prices", 5.0)
simulate (count, "Lombra distribution of prices", 11.25, True)
simulate (count, "Lombra distribution of prices", -99)

prices = []
price_counts = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] # 10 tails

price_range (9, 4 * 800)
price_range (8, 4 * 80)
price_range (5, 4 * 60)
price_range (4, 4 * 20)
price_range (7, 4 * 20)
price_range (0, 4 * 16)
price_range (1, 4 * 1)
price_range (2, 4 * 1)
price_range (3, 4 * 1)
price_range (6, 4 * 1)

simulate (count, "Imagined distribution of prices", 0.0, True)
simulate (count, "Imagined distribution of prices", 5.0)
simulate (count, "Imagined distribution of prices", 11.25)
simulate (count, "Imagined distribution of prices", -99)

print ('nFini.')

Blues Heron

(7,105 posts)
2. Won't it just take us back to how things were when the penny had the buying power of todays nickel?
Mon May 26, 2025, 12:27 AM
May 26

When was that- the 1970s? Getting rid of the penny would give the granularity of currency we had back then.

Latest Discussions»Culture Forums»Science»The Joys of Being Wrong, ...