Следующая задача пришла от наших бухгалтеров.
Бухгалтерская программа должна уметь проводить операции c различными агентами, как c физическими/юридическими лицами, так и с иностранными компаниями: чп, ип, ооо, зао, иклмн, ёпрст.
С некоторых операций налог платить не нужно, некоторые облагаются подоходным налогом, с некоторых необходимо уплатить НДС.
Необходимо расширить функциональность класса Bill
возможностью работы с различными системами налогообложения.
Практически в любом языке программирования, если вы напишете нечто подобное, вы получите не то, что вы могли ожидать:
System.out.println(0.1 + 0.2); // => 0.30000000000000004
Связанно это с тем, что числа в компьютере хранятся в двоичном виде, и конечные дроби в десятичной системе счисления 0.1
и 0.2
превращаются в бесконечные периодические дроби в двоичной системе счисления 0.00011001100110011010
и 0.00110011001100110011
соответственно.
Как следствие, часть числа теряется, а значит, и точность при операциях с ними.
Подробнее про представление вещественных чисел в компьютере на сайте ИТМО.
Для того чтобы производить расчёты с десятичными дробными числами, существует специальный класс BigDecimal
(большое десятичное). Большое, потому что у него нет стандартных ограничений как у double (-1.7E+308 до 1.7E+308)
или int (-2147483648 до 2147483647)
.
Этот класс может хранить число, состоящее из 2,147,483,647 цифр, Карл! А десятичное, потому что каждая цифра числа хранится по отдельности, из-за чего не возникает проблем с потерями при переводе между системами счисления.
В задаче предлагается принять некоторые неточности и использовать уже знакомый нам тип double. Плюсиком будет решение задачи № 2*
(со звёздочкой).
→ Создание нескольких счетов и расчет налогов для них.
- Класс
Bill
В системе уже есть класс Bill
, в который мы добавили поле TaxType taxType;
и метод payTaxes()
:
class Bill {
private double amount;
private TaxType taxType;
private TaxService taxService;
public Bill(double amount, TaxType taxType, TaxService taxService) {
this.amount = amount;
this.taxType = taxType;
this.taxService = taxService;
}
public void payTaxes() {
// TODO вместо 0.0 посчитать размер налога исходя из TaxType
double taxAmount = 0.0;
taxService.payOut(taxAmount);
}
}
А также класс налоговой службы:
class TaxService {
public void payOut(double taxAmount) {
System.out.format("Уплачен налог в размере %.2f%n", taxAmount);
}
}
- Создадим классы для различных типов налогообложения.
- Базовый класс
class TaxType {
public double calculateTaxFor(double amount) {
// TODO override me!
return 0.0;
}
}
- Классы, расширяющие
TaxType
:- Подоходный налог, = 13% (
IncomeTaxType
) - НДС, = 18% (
VATaxType
) - Прогрессивный налог, до 100 тысяч = 10%, больше 100 тысяч = 15% (
ProgressiveTaxType
)
- Подоходный налог, = 13% (
- В методе
main
создадим несколько счетов и оплатим с них налоги в налоговую службу.
public static void main(String[] args) {
TaxService taxService = new TaxService();
Bill[] payments = new Bill[] {
// TODO создать платежи с различным типами налогообложения
};
for (int i = 0; i < payments.length; ++i) {
Bill bill = payments[i];
bill.payTaxes();
}
}
Здесь необходимо реализовать тот же функционал, но используя вместо double –> BigDecimal.
Работа с ним может показаться необычной и странной. Например, их нельзя сложить, используя оператор +
(в Java запрещено перегружать операторы), или сравнить с помощью ==
(так как это объект, произойдёт сравнение ссылок, а не значений объектов). Вместо этого мы должны использовать методы .add(…)
и .compareTo(…)
соответственно.
Экземпляр этого класса можно создать с помощью new BigDecimal("0.1")
или BigDecimal.valueOf(0.2)
.
Как и String
, экземпляры этого класса неизменяемые (иммутабельные), а методы .add(…)
, .multiply(…)
возвращают новый объект, содержащий результат операции.
Например, чтобы сложить 0.1 рубля и 0.2 рубля и получить ожидаемые 30 копеек, мы могли бы написать код:
BigDecimal first = new BigDecimal("0.10");
BigDecimal second = BigDecimal.valueOf(0.2);
BigDecimal sum = first.add(second);
System.out.println(sum.toString()); // => 0.30
При делении BigDecimal нужно обязательно указывать способ округления результата. Про способы округления чисел в BigDecimal
можно почитать например, тут.