Как исправить неправильный подсчёт копеек?
Моя проблема в том, что не правильно подсчитаны копейки в раздели накопления, так как я не понимаю как реализовать это условие в задаче:
Сумма доходов равна 50 001.25 , 10 % от этой суммы это 5000.125 рублей. Пол копейки как валюты не существует, поэтому эта половинка переходит в накопления."
Вот вся задача, свой код прикреплю ниже:
Семья решила заняться оптимизацией своих денежных расходов и придумала следующую схему:
10 % доходов идут на отпуск
30 % доходов на пропитание и еду
5 % на коммунальные платежи
15 % на выходной досуг
остальные 40% идут в накопления
Если вдруг нужный процент не получается сделать, тогда копейка перекидывается в накопления. Например:
Сумма доходов равна 50 001.25, 10 % от этой суммы это 5000.125 рублей. Пол копейки как валюты не существует, поэтому эта половинка переходит в накопления.
Напишите для семьи программу, которая будет принимать на вход месячный доход мужа и жены и рассчитывать сколько им нужно отложить на каждую категорию.
Ваша программа принимает два числа типа float. Целая часть – рубли, а дробная – копейки.
В качестве результата работы выведите количество рублей и копеек для каждой из категорий в таком формате:
Отпуск: 10 руб. 5 коп.
Пропитание и еда: 30 руб. 15 коп.
Коммунальные платежи: 5 руб. 0 коп.
Досуг: 10 руб. 11 коп.
Накопления: 50 руб. 3 коп.
Мой код:
mather = float(input())
dad = float(input())
income = mather + dad
#Отпуск 10%
otp = income * 0.10
rubOtp = int(otp)
kopOtp = int((100*(otp-rubOtp))% 100)
#Пропитание и еда 30%
eda = income * 0.30
rubEda = int(eda)
kopEda = int((100*(eda-rubEda))%100)
#Комунальные услуги 5%
kom = income * 0.05
rubKom = int(kom)
kopKom = int((100*(kom-rubKom))%100)
#Досуг 15%
dos = income * 0.15
rubDos = int(dos)
kopDos = int((100*(dos-rubDos))%100)
#Накопление 40%
nak = income * 0.40
rubNak = int(nak)
kopNak = int((100*(nak-rubNak))%100)
print("Отпуск:", rubOtp,'руб.', kopOtp,'коп.')
print("Пропитание и еда:", rubEda,'руб.', kopEda,'коп.')
print("Коммунальные платежи:", rubKom,'руб.', kopKom,'коп.')
print("Досуг:", rubDos,'руб.', kopDos,'коп.')
print("Накопления:", rubNak,'руб.', kopNak,'коп.')
Задания:
Sample Input 1:
30000.50
20000.75
Sample Output 1:
Отпуск: 5000 руб. 12 коп.
Пропитание и еда: 15000 руб. 37 коп.
Коммунальные платежи: 2500 руб. 6 коп.
Досуг: 7500 руб. 18 коп.
Накопления: 20000 руб. 52 коп.
Sample Input 2:
123.45
24.56
Sample Output 2:
Отпуск: 14 руб. 80 коп.
Пропитание и еда: 44 руб. 40 коп.
Коммунальные платежи: 7 руб. 40 коп.
Досуг: 22 руб. 20 коп.
Накопления: 59 руб. 21 коп.
Sample Input 3:
123
234
Sample Output 3:
Отпуск: 35 руб. 70 коп.
Пропитание и еда: 107 руб. 10 коп.
Коммунальные платежи: 17 руб. 85 коп.
Досуг: 53 руб. 55 коп.
Накопления: 142 руб. 80 коп.
Sample Input 4:
100
200
Sample Output 4:
Отпуск: 30 руб. 0 коп.
Пропитание и еда: 90 руб. 0 коп.
Коммунальные платежи: 15 руб. 0 коп.
Досуг: 45 руб. 0 коп.
Накопления: 120 руб. 0 коп.
Sample Input 5:
30000.55
20000.75
Sample Output 5:
Отпуск: 5000 руб. 13 коп.
Пропитание и еда: 15000 руб. 39 коп.
Коммунальные платежи: 2500 руб. 6 коп.
Досуг: 7500 руб. 19 коп.
Накопления: 20000 руб. 53 коп.
Sample Input 6:
32344.45
34543.23
Sample Output 6:
Отпуск: 6688 руб. 76 коп.
Пропитание и еда: 20066 руб. 30 коп.
Коммунальные платежи: 3344 руб. 38 коп.
Досуг: 10033 руб. 15 коп.
Накопления: 26755 руб. 9 коп.
Sample Input 7:
30000.55
20000.85
Sample Output 7:
Отпуск: 5000 руб. 14 коп.
Пропитание и еда: 15000 руб. 42 коп.
Коммунальные платежи: 2500 руб. 7 коп.
Досуг: 7500 руб. 21 коп.
Накопления: 20000 руб. 56 коп.
Ответы (2 шт):
Вещественные числа не годятся для того чтобы точно представлять десятичные дроби:
@>>> 0.3 0.3 @>>> f'{0.3:.20f}' '0.29999999999999998890'
Десятичная дробь 0.3 внутри компьютера хранится в виде очень близкого но другого числа. Сосчитаем налог 30% от 101 рубля. Должно получиться 30 рублей 30 копеек. А на деле немного меньше:
@>>> 101 * 0.3 30.299999999999997
Вычитаем целые рубли и умножаем на 100 чтобы перевести число в копейки:
@>>> 100 * (101 * 0.3 - 30) 29.999999999999716
Получилось немного меньше 30 копеек, как должно бы быть. Сейчас произойдет катастрофа - небольшая ошибка приведёт к потере копейки:
@>>> int(100 * (101 * 0.3 - 30)) 29
Вещественные числа придуманы для инженерных расчётов. При их проектировании ставились две цели - точность и быстрота. И они действительно точные и быстрые. То что 0.3 нельзя записать в виде компьютерного числа - не важно. Его можно представить с огромной точностью - а это и нужно для инженерных расчётов. И совсем не годится для бухгалтерии.
Можно аккуратно подправить вычисления так чтобы вычисления стали верными. Но это сложный путь - надо будет доказывать что учтены все возможные ошибки представления для исходных чисел и все ошибки округления для операций. Есть решение проще: использовать точные целые копейки вместо приближенных вещественных рублей.
Сразу нарушу собственное правило: для простоты чтения вещественные рубли всё-таки будут использованы. Но они сразу же переводятся в копейки. Это решение будет ошибаться, но только для очень больших, астрономических, сумм:
def get_money(s):
return int(round(100 * float(s)))
Проценты тоже будут хранится как целые числа. Для получения процента от суммы сумма и процент перемножаются и делятся на сто. Результат округляется вниз к целому:
def get_percent(money, percent):
return money * percent // 100
Основной код читает доходы, складывает их, вычисляет проценты - всё в целых копейках. Из-за округления вниз сумма расходов может быть меньше суммы дохода. Ошибка income - sum(expenses) добавляется к последнему расходу в списке:
def main():
percents = (
('Отпуск' , 10),
('Пропитание и еда' , 30),
('Коммунальные платежи', 5),
('Досуг' , 15),
('Накопления' , 40)
)
income = get_money(input()) + get_money(input())
expenses = [get_percent(income, p[1]) for p in percents]
expenses[-1] += income - sum(expenses)
assert sum(expenses) == income
for (w, _), e in zip(percents, expenses):
print(f'{w}: {e // 100} руб. {e % 100} коп.')
main()
Для операций с деньгами можно использовать класс
Decimalиз стандартного модуля decimal.Все операции с ним примерно как с float (кроме создания объекта), но его преимущество в том, что он хранит число именно так как мы его видим в десятичном формате, никаких неожиданностей типа
0.1 + 0.2 != 0.3.Из особенностей - при операциях с объектами
Decimal(типа сложения/вычитания/умножения/деления) нельзя использоватьDecimalиfloatвместе, можно использоватьintили другойDecimal. Например,Decimal("1000.0") * 0.15не будет работать, вместо этого нужно писатьDecimal("1000.0") * Decimal("0.15")илиDecimal("1000.0") * 15 / 100.Но в целом
Decimalработает, конечно, медленнее чемfloat(просто из-за того что это более сложный формат, сам модульdecimalпри этом реализован на Си).Чтобы дробную часть превратить в копейки, нужно просто умножить на 100 и привести к целому, т.е. вместо
kopOtp = int((100*(otp-rubOtp))% 100)пишитеkopOtp = int((otp - rubOtp) * 100)Чтобы все дробные копейки переходили в накопления, а не терялись, накопления нужно считать не через процент, а из общей суммы доходов вычесть все расходы
from decimal import Decimal
# mather = Decimal(input())
# dad = Decimal(input())
# income = mather + dad
income = Decimal("50001.25")
#Отпуск 10%
otp = income * 10 / 100
# .quantize(Decimal(".01")) округляет результат до .01 (т.е. до 1 копейки в нашем случае)
otp = otp.quantize(Decimal(".01"))
rubOtp = int(otp)
kopOtp = int((otp - rubOtp) * 100)
#Пропитание и еда 30%
eda = income * 30 / 100
eda = eda.quantize(Decimal(".01"))
rubEda = int(eda)
kopEda = int((eda - rubEda) * 100)
#Комунальные услуги 5%
kom = income * 5 / 100
kom = kom.quantize(Decimal(".01"))
rubKom = int(kom)
kopKom = int((kom - rubKom) * 100)
#Досуг 15%
dos = income * 15 / 100
dos = dos.quantize(Decimal(".01"))
rubDos = int(dos)
kopDos = int((dos - rubDos) * 100)
#Все остальное идет в накопление
nak = income - otp - eda - kom - dos
nak = nak.quantize(Decimal(".01"))
rubNak = int(nak)
kopNak = int((nak - rubNak) * 100)
print("Отпуск:", rubOtp,'руб.', kopOtp,'коп.')
print("Пропитание и еда:", rubEda,'руб.', kopEda,'коп.')
print("Коммунальные платежи:", rubKom,'руб.', kopKom,'коп.')
print("Досуг:", rubDos,'руб.', kopDos,'коп.')
print("Накопления:", rubNak,'руб.', kopNak,'коп.')
Вывод:
Отпуск: 5000 руб. 12 коп.
Пропитание и еда: 15000 руб. 38 коп.
Коммунальные платежи: 2500 руб. 6 коп.
Досуг: 7500 руб. 19 коп.
Накопления: 20000 руб. 50 коп.