1.08a + 10.8b1.08(a + b) って同じだろ

こないだ Python のワークショップごっこをしていたら、こんな話が出てな。

  • 緑「消費税って、税抜を合計してからかけても、個別にかけてから合計しても同じだろ?」
  • ルームメイト「違うでしょ?」
# 緑「だってこうなるだろ?」
1.08*a + 1.08*b == 1.08 * (a + b)

小数点以下をいじるとズレる

小数点以下を切り捨てたり四捨五入して計算するとズレる。

どんくらいズレるのか

1〜1000円の商品を100個ランダム生成して、まとめて会計するのと個別に会計するのでどれくらい差が出るのか試す。税抜を合計して消費税加算するのと、個別に消費税加算して合計するのを比べるってことね。

計算方法 まとめて会計 個別に会計 差額
小数点以下いじらず計算 57538.08 57538.08 0.00
小数点以下切り捨て計算 57538 57491 47
小数点以下四捨五入計算 57538 57535 3

こんくらいズレる。てか四捨五入でもズレるのか。ちなみに四捨五入のほうはたまに個別に消費税かけたほうが高くなる。

じゃあレジを何周もして1商品ずつお会計したほうがお得なのか

切り捨て方式の場合、商品数に比例してお得になるみたいだ。発生する誤差の平均値を出してみた。

商品個数 平均価格差
2 0.4
5 1.9
10 4.3
20 9.1
30 14.0
50 23.5
70 33.1
90 42.5
100 47.7
500 239.3
1000 479.6

ただし商品個数5個(レジ5周)でも2円程度のお得だ。これはまとめて会計しちゃうべきだろう。

ちなみに四捨五入方式の場合ほとんど誤差はないみたい。

商品個数 平均価格差
2 -0.00
5 -0.00
10 -0.03
20 -0.04
30 -0.14
50 0.06
70 -0.05
90 0.13
100 -0.16
500 0.08
1000 -0.66

算出コード

あってるよな??

from random import randint
from decimal import Decimal
from math import floor


# 消費税
VAT = Decimal('1.08')

# 平均値
def GetAvr(itr):
    lis = list(itr)
    return sum(lis) / len(lis)

# 商品ランダム生成
def CreateRandomPrices(price_range, goods_num):
    nums = [ Decimal(str(randint(price_range[0],price_range[1]))) for i in range(goods_num) ]
    taxed_num = [ i * VAT for i in nums ]
    return nums, taxed_num

# 消費税計算を共通化。
def CalcVat(nums, taxed_num, func=None):
    if func is None:
        func = lambda _: _
    _1 = func(sum(nums) * VAT)
    _2 = sum(map(func, taxed_num))
    return (_1, _2, _1-_2)
# 1~1000円の商品を100個生成して、会計1回と会計100回での支払い金額差を算出。
nums, taxed_num = CreateRandomPrices((1,1000), 100)
print( '小数点以下いじらず計算:', CalcVat(nums, taxed_num) )
print( '小数点以下切り捨て計算:', CalcVat(nums, taxed_num, floor) )
print( '小数点以下四捨五入計算:', CalcVat(nums, taxed_num, round) )


# 商品個数ごとの誤差平均値を算出。
GOODS_NUMS = [2, 5, 10, 20, 30, 50, 70, 90, 100, 500, 1000]
for goods_num in GOODS_NUMS:
    # 500パターンほどランダムで商品生成して平均を出そう。
    results_floor = []
    results_round = []
    for i in range(500):
        nums, taxed_num = CreateRandomPrices((1,1000), goods_num)
        results_floor.append( Decimal(str(CalcVat(nums, taxed_num, floor)[2])) )
        results_round.append( Decimal(str(CalcVat(nums, taxed_num, round)[2])) )
    print( f'切り捨て  商品個数{goods_num}  平均価格差{GetAvr(results_floor)}' )
    print( f'四捨五入  商品個数{goods_num}  平均価格差{GetAvr(results_round)}' )