Шеста задача
- Краен срок:
- 16.01.2013 17:00
- Точки:
- 6
Срокът за предаване на решения е отминал
В тази задача ще искаме от вас да съставите няколко малки класа, които ще моделират работа с пари и валути.
BigDecimal
Както знаем, аритметиката с числа с плаваща запетая (floats) е приблизителна. Ако искаме да работим с пари, или ако по друга причина ни трябва точна аритметика, то тогава типът "float" е забранена дума. Това е валидно във всички езици за програмиране, имащи тип "float", отговарящ на C-типа float
.
За точна аритметика с десетични числа, в Ruby се ползва класът от стандартната библиотека BigDecimal
. Може да го включите във вашето приложение с require 'bigdecimal'
. С BigDecimal
работите като с всеки друг Numeric
. Можете да създавате обекти от този клас от низове, съдържащи числото, например така:
BigDecimal.new '12.45'
BigDecimal.new('0.0').zero? # => true
Допълнително, ако включите и require 'bigdecimal/util'
, ще може да ползвате метод #to_d
върху низове и други обекти, по следния начин:
'12.45'.to_d
1.to_d
3.14.to_d
BigDecimal('0').to_d
Вижте документацията му за повече детайли.
Валути и обменни курсове
За да моделираме цени и пари нашия електронен магазин, ще въведем концепцията за валута и обменен курс. Валута ще представвяме като символ от три букви, според съответния ISO стандарт, например :BGN
, :USD'
, :EUR
и прочее.
Ще считаме, че навсякъде, където се очкава да бъде подадена валута, ще се подава валидна такава и няма нужда да ги проверявате за валидност.
Целта е да съставим клас ExchangeRate
, който моделира нашата идея за обмен между суми в различни валути. Ще приемем, че ако имаме курс за обмяна между валутите А и Б, то ще можем да обменяме и между Б и А, по съответния реципрочен курс.
Този клас трябва да има следните инстанционни методи и характеристики:
-
set(from_currency, to_currency, rate)
, който задава курс на обмен между двете подадени валути. Валутите се подават като символи, а обменният курс — катоBigDecimal
. Извикване на този метод задава колко отto_currency
ще получим за една единица отfrom_currency
, както и обратния реципрочен курс за обмен в обратна посока между двете валути. След извикване на метода, можем да обръщаме между двете валути в произволен ред, по съответния курс. Методът може да бъде извикван многократно. -
get(from_currency, to_currency)
трябва да върне обменния курс между двете подадени валути. Ако няма дефиниран курс между двете валути, трябва да връщаnil
. Ако има такъв курс, то той трябва да работи и в двете посоки, тоест:rate = ExchangeRate.new rate.set :USD, :BGN, '1.6'.to_d rate.get(:USD, :EUR) # => nil rate.get(:USD, :BGN) # => BigDecimal.new('1.6') rate.get(:BGN, :USD) # => BigDecimal.new('.625')
convert(from_currency, to_currency, amount)
се ползва да обърнеamount
, подаден катоBigDecimal
, от валутатаfrom_currency
вto_currency
. Резултатът от този метод трябва да е същоBigDecimal
. Ако обменният курс между двете подадени валути е неизвестен, трябва да бъде хвърлена грешкатаExchangeRate::Unknown
, която трябва да наследява отRuntimeError
.- Обменният курс между две еднакви валути е винаги единица (очевидно) и това не трявба да може да се променя.
Пари
Когато говорим за валути и обменни курсове, не може да става дума за пари, без редом със сумата да върви и валута.
Ще моделираме идеята за "пари", създавайки клас Money
. Конструираме дадена сума пари по следния начин:
Money.new '49.98'.to_d, :USD
Така създаденият обект трябва да може да връща сумата през инстанционен метод amount
, а валутата — през currency
. Допълнително, всеки Money
обект трябва да отговаря на следните инстанционни методи:
-
to_s
дава текстово представяне на обекта пари от следния вид, като винаги показва точно два знака след десетичната запетая:bucks = Money.new 100.to_d, :USD bucks.to_s # => '100.00 USD' cents = Money.new '0.443'.to_d, :USD cents.to_s # => '0.44 USD'
in(currency, exchange_rate)
ще върне нов обект от типMoney
, който е с подадената валута и със сума, конвертирана в подадената валута според подадените обменни курсове в подадения обект от типExchangeRate
. Ако обменът не може да се осъществи, трябва да бъде хвърлено изключениеExchangeRate::Unknown
. Тук се ползва същата семантика, както при обменните курсове от по-горе.- Аритметични операции
*
,/
могат да се ползват сNumeric
обекти, докато+
и-
трябва да работят с другиMoney
обекти. Когато операндите са два обекта от типMoney
, то валутата им трябва да е еднаква, иначе трябва да се предизвикаMoney::IncompatibleCurrencies
. Опит за изпълнение на въпросните операции с обекти от друг тип трябва да предизвикваArgumentError
. - Следните операции за сравнение трябва да работят спрямо други обекти от тип
Money
, когато валутата им е еднаква:<=>
,==
,<
,<=
,>
,>=
. При сравнение на различни валути трябва да се предизвикаMoney::IncompatibleCurrencies
, а при сравнение наMoney
с друг тип обект —ArgumentError
.
Ограничения
Тази задача има следните ограничения:
- Най-много 100 символа на ред
- Най-много 5 реда на метод
- Най-много 2 нива на влагане
Ако искате да проверите дали задачата ви спазва ограниченията, следвайте инструкциите в описанието на хранилището за домашните.
Няма да приемаме решения, които не спазват ограниченията. Изпълнявайте rubocop
редовно, докато пишете кода. Ако смятате, че rubocop
греши по някакъв начин,
пишете ни на fmi@ruby.bg, заедно с прикачен код или линк към такъв като
private gist. Ако пуснете кода си публично (например във форумите), ще смятаме
това за преписване.