CSC/ECE 517 Fall 2011/ch7 7a kr: Difference between revisions
(32 intermediate revisions by 2 users not shown) | |||
Line 79: | Line 79: | ||
The python-money implementation of storing money in an object is a relatively simple library written in Python. As such, it does not have as much functionality nor the complexities that many other implementations have. | The python-money implementation of storing money in an object is a relatively simple library written in Python. As such, it does not have as much functionality nor the complexities that many other implementations have. | ||
The python-money module consists of two classes, '''Money''' and '''Currency'''. Much like other implementations, the '''Money''' object contains an amount and currency internally. The amount is specified as type Decimal<ref name = pydecimal />, which allows storing the amount in a format similar to a float but without all of the precision issues that arise with using floats. When a '''Money''' object is created, either an identifier or '''Currency''' object is passed. Below is an example of the '''Currency'' data used for USD: | The python-money module consists of two classes, '''Money''' and '''Currency'''. Much like other implementations, the '''Money''' object contains an amount and currency internally. The amount is specified as type Decimal<ref name = pydecimal />, which allows storing the amount in a format similar to a float but without all of the precision issues that arise with using floats. When a '''Money''' object is created, either an identifier or '''Currency''' object is passed. Below is an example of the '''Currency''' data used for USD: | ||
<pre> | <pre> | ||
CURRENCY['USD'] = Currency(code='USD', numeric='840', name='US Dollar', countries=['AMERICAN SAMOA', 'BRITISH INDIAN OCEAN TERRITORY', 'ECUADOR', 'GUAM', 'MARSHALL ISLANDS', 'MICRONESIA', 'NORTHERN MARIANA ISLANDS', 'PALAU', 'PUERTO RICO', 'TIMOR-LESTE', 'TURKS AND CAICOS ISLANDS', 'UNITED STATES MINOR OUTLYING ISLANDS', 'VIRGIN ISLANDS (BRITISH)', 'VIRGIN ISLANDS (U.S.)']) | CURRENCY['USD'] = Currency(code='USD', numeric='840', name='US Dollar', | ||
countries=['AMERICAN SAMOA', 'BRITISH INDIAN OCEAN TERRITORY', 'ECUADOR', 'GUAM', | |||
'MARSHALL ISLANDS', 'MICRONESIA', 'NORTHERN MARIANA ISLANDS', 'PALAU', | |||
'PUERTO RICO', 'TIMOR-LESTE', 'TURKS AND CAICOS ISLANDS', | |||
'UNITED STATES MINOR OUTLYING ISLANDS', 'VIRGIN ISLANDS (BRITISH)', | |||
'VIRGIN ISLANDS (U.S.)']) | |||
</pre> | </pre> | ||
Line 88: | Line 93: | ||
=== Advantages === | === Advantages === | ||
* Much like an integer value in Ruby, a Decimal<ref name = pydecimal /> has a theoretically infinite precision and maximum value. | * Much like an integer value in Ruby, a Decimal<ref name = pydecimal /> has a theoretically infinite precision and maximum value. | ||
* As long as the user does not access the internal structure of the '''Money''' object directly, it behaves as an immutable object | |||
=== Disadvantages === | === Disadvantages === | ||
* This implementation does not support an arbitrary number of decimal points | * This implementation does not support an arbitrary number of decimal points | ||
* Since the currency conversion data is set within the '''Currency''' object itself | * Since the currency conversion data is set within the '''Currency''' object itself | ||
** There is no locking present for the '''Currency''' object and there could be race conditions when users are simultaneously setting and getting exchange rates | ** There is no locking present for the '''Currency''' object and there could be race conditions when users are simultaneously setting and getting exchange rates | ||
** The current implementation can only convert to and from the default currency type | ** The current implementation can only convert to and from the default currency type | ||
* Since this implementation can take multiple types of arguments as the monetary value but does not add methods to primitives for converting them to '''Money''' objects, type checking has to be done on arguments to determine how to handle them. This is something that we want to avoid as part of object oriented design<ref name = skrien />. | * Since this implementation can take multiple types of arguments as the monetary value but does not add methods to primitives for converting them to '''Money''' objects, type checking has to be done on arguments to determine how to handle them. This is something that we want to avoid as part of object oriented design<ref name = skrien />. | ||
== Java == | |||
=== Overview === | |||
While Skrien introduces the Java Money class and then refines it through various stages to break it up into SimpleMoney and MixedMoney derived classes, there are various other implementations possible in Java itself. Some of the most common operations for money types are implemented in the <ref name = Currency />[http://docs.oracle.com/javase/1.4.2/docs/api/java/util/Currency.html Currency] class, which encapsulates, among other things, standard identifiers for currencies around the world. However, the lack of a standard Money Class in the Java library has resulted in users employing different schemes to conduct operations on money. | |||
One of the simplest ways to represent Money in Java programs is to use one of the following primitive types: | |||
* float | |||
* double | |||
* int | |||
* long | |||
* BigDecimal | |||
* Money Class (custom wrapper around BigDecimal) | |||
Out of these, BigDecimal is the fundamental building block and custom Money Class implementations simply provide a higher level of abstraction around the BigDecimal type. This makes it a more practical form of use. | |||
Here is an example snippet from [http://www.javapractices.com/topic/TopicAction.do?Id=13] that shows the use of BigDecimal to perform some basic calculations on Money values <ref name = jsql/>. | |||
<pre> | |||
import java.math.BigDecimal; | |||
import java.util.Currency; | |||
public class MoneyCalculation { | |||
private BigDecimal fAmountOne, fAmountTwo; | |||
private static int DECIMALS = 2; | |||
private static final BigDecimal TWO = new BigDecimal("2"); | |||
private static int ROUNDING_MODE = BigDecimal.ROUND_HALF_EVEN; | |||
private BigDecimal getSum(){ | |||
return fAmountOne.add(fAmountTwo); | |||
} | |||
private BigDecimal getAverage(){ | |||
return getSum().divide(TWO, ROUNDING_MODE); | |||
} | |||
} | |||
</pre> | |||
One of the special cases of Java Money class implementation is found in Java Object-Relational Database Systems [http://www.firstsql.com/javaobjects.shtml]. Objects that are defined in Java are stored in FirstSQL/J as values in database columns. These are normal columns whose type is a Java class that is mapped to the appropriate database type internally. | |||
<pre> | |||
Money Class | |||
Constructors: | |||
Money(BigDecimal amt, String currency); | |||
Money(double amt, String currency); Note: currency is a string name of a currency – ‘USD’, ‘Euro’, … | |||
Methods: String getCurrency(); // get currency type string | |||
String toString(); // get amount with standard formatting | |||
BigDecimal decValue(); // get numeric amount | |||
double doubleValue(); // get numeric amount | |||
</pre> | |||
=== Advantages === | |||
* The int and long primitive types are very simple to use while representing cents/pennies. | |||
* The BigDecimal representation has built-in rounding modes. | |||
* BigDecimal objects are immutable i.e. operations always return new objects, and do not modify the state of existing objects. | |||
* The use of Currency class in custom Money implementations allows users to represent money in any currency. Conversions from one currency to another can be easily done with the use of a Currency object. | |||
* An object oriented approach to Money allows encapsulation of methods such as adding, subtracting, negating money in one common implementation. | |||
* The FirstSQL/J inbuilt querying capabilities provide significant elegant methods to get information about Money classes. | |||
=== Disadvantages === | |||
* Depending on the number of digits (<=9, <=18, >18), the corresponding types int, long, BigDecimal respectively are recommended for representation. This requires the user to be aware of what values the representation needs to take up. | |||
* Primitive types double and float carry small rounding differences. | |||
* Significant amount of logic is required for custom implementation of Mixed Money - different currencies being available. Certain inefficiencies of code can creep in if the user is not careful with some of the implementation details. | |||
== C++ == | |||
=== Overview === | |||
Representations similar to the ones in Java are also seen in C++. While the fundamentals are the same, there are a few subtle differences as well. There are certain functions like "floor" that can be used in C++ for aiding in the right precision with decimal values for money quantities. The underlying data type for such cases is double. | |||
Eg. floor(double*21.32). | |||
With this neat trick, decimals are not lost because the double type, with its precision being greater than or equal to 15 digits, is used as a compiler supported long long. The only thing to be careful of is multiplying and dividing such values. | |||
The one cool feature is that the Zortech distribution has an in-built C++ Money Class in it. | |||
<pre> | |||
class money { | |||
long dollars; | |||
int cents; | |||
// ... | |||
}; | |||
</pre> | |||
=== Advantages === | |||
* The Zortech C++ money class is an efficient implementation. This is due to the fact that the operators use integer arithmetic. | |||
* Not much overhead with maintaining the money class objects. | |||
* Some nice routines such as "flatten" are made available using the money class. An example of this implementation from [http://www.di-mare.com/adolfo/p/money.htm] is shown here. | |||
<pre> | |||
// Returns a money data item where the cents are | |||
// rounded modulo "cents". In this way cents can | |||
// be stripped of money items when the currency | |||
// does not have all the coins required to pay | |||
// every posible quantity. | |||
money flatten(const money& m, double cents, int rounding) { | |||
money temp; | |||
double c = floor(fabs(cents*money::SCALE())); // cents | |||
double r = fmod(m.m_money, c); // remainder | |||
temp.m_money = | |||
(!rounding || (2.0* r <= c) | |||
? m.m_money - r | |||
: m.m_money - r + c | |||
); | |||
return temp; | |||
} | |||
</pre> | |||
=== Disadvantages === | |||
* The long datatype in the Zortech implementation cannot hold more than 10 decimal digits. | |||
* This money class implements most arithmethic operators, but it does not implement the multiply and divide operations. The user is left to implement these. | |||
== References == | == References == | ||
Line 103: | Line 221: | ||
<ref name = pydecimal> http://docs.python.org/library/decimal.html </ref> | <ref name = pydecimal> http://docs.python.org/library/decimal.html </ref> | ||
<ref name = iso4217> http://www.abstracttechnology.com/standard/iso4217.html </ref> | <ref name = iso4217> http://www.abstracttechnology.com/standard/iso4217.html </ref> | ||
<ref name = Currency> http://docs.oracle.com/javase/1.4.2/docs/api/java/util/Currency.html </ref> | |||
<ref name = jsql> http://www.firstsql.com/javaobjects.shtml </ref> | |||
</references> | </references> |
Latest revision as of 15:11, 2 December 2011
CSC/ECE 517 Fall 2011/ch7 7a kr
7a. Representing money. Skrien Chapter 6 gives an example of a class that can be used to represent money. But how is it done in real programs? Investigate, and report on the advantages and disadvantages of other approaches vs. Skrien's.
Introduction
A common task in real world application programs is conducting operations on and manipulating money values. The base data type used to represent money can be one of several possible implementations. Object oriented languages have the ability to represent this information using classes. Primitive data types such as integers, floating point numbers can also be used to represent money. There are certain inherent advantages and disadvantages with each of these methods of representation. This wiki article explores these different implementations and analyzes their relative merits and demerits. We also focus on the various operations that can be performed with money values and analyze how different languages and schemes help while working with money values.
Money Gem<ref name = moneygemwebsite /> (Ruby)
Overview
The Ruby Money gem borrows quite a bit from the Skrien<ref name = skrien /> text methodologies while using the dynamic properties of Ruby to allow for more flexibility. However, it should be noted that this implementation does not support an object containing mixed currencies as mentioned in the Skrien text and some behavior implemented goes against it's principles.
The Money gem consists of a main Money class which houses several sub-classes and modules which allow for everything to be done from the main Money class directly without the users knowledge. This implementation also mostly sticks to the idea that a Money object is immutable and thus any conversions and operations result in a new Money object. The only exception to this is that the currency identifier for a Money object can be changed once it has been created.
Internally, a Money object stores money as an integer in the form of cents. It should be noted that while the variable is called cents, it can apply to all currencies which have a subunit of denomination. Similar to the way the Skrien text uses the Currency and CurrencyConverter classes, the Money class uses internal classes for currency information and currency conversion as well, called Currency and Bank respectively. The Money object stores a bank and currency as well as default bank and currency types at the class level.
The Currency class contains information such as the ISO4217<ref name = iso4217 /> three letter and number codes for common currencies as well as number of subunits in a denomination (ie, cents in a dollar) and delimiters for units. Through the use of the currency class, Money objects can be initialized with a Currency value and have specific output formats based on currency. The currency class also internally extends the CurrencyLoader class which allows for specifying different locations for loading currency information to allow for flexible currency management. Below is an example of the USD currency information loaded by a default JSON configuration file:
"usd": { "priority": 1, "iso_code": "USD", "name": "United States Dollar", "symbol": "$", "subunit": "Cent", "subunit_to_unit": 100, "symbol_first": true, "html_entity": "$", "decimal_mark": ".", "thousands_separator": ",", "iso_numeric": "840" },
The Bank class is also an internal class to the Money class but it resides at the class level. This means that the Bank object follows the Singleton pattern in that only one Bank object exists across all of the currency. The purpose of the Bank is to maintain information related to currency values such that currency conversion can take place. Since the Bank class inherits from an interface, this allows for extensible money conversion schemes such as being able to 'scrape' data from the internet and use it to populate conversion ratios.
The Money class also has multiple modules internally that expand the operations that can be performed on Money objects, including converting to and from numbers, strings and symbols. Through the use of Ruby's dynamic nature, to_money() methods are added to the Numeric, String and Symbol types.
Below is an example from the official website <ref name = moneygemwebsite />:
require 'money' # 10.00 USD money = Money.new(1000, "USD") money.cents #=> 1000 money.currency #=> Currency.new("USD") # Comparisons Money.new(1000, "USD") == Money.new(1000, "USD") #=> true Money.new(1000, "USD") == Money.new(100, "USD") #=> false Money.new(1000, "USD") == Money.new(1000, "EUR") #=> false Money.new(1000, "USD") != Money.new(1000, "EUR") #=> true # Arithmetic Money.new(1000, "USD") + Money.new(500, "USD") == Money.new(1500, "USD") Money.new(1000, "USD") - Money.new(200, "USD") == Money.new(800, "USD") Money.new(1000, "USD") / 5 == Money.new(200, "USD") Money.new(1000, "USD") * 5 == Money.new(5000, "USD") # Currency conversions some_code_to_setup_exchange_rates Money.new(1000, "USD").exchange_to("EUR") == Money.new(some_value, "EUR")
Advantages
- The internal CurrencyLoader class allows for easily extensible currency information sources, even allowing currency information to be dynamically loaded
- Due to the Bank implementation, additional class implementations can be loaded and changed out at runtime to allow for more complex, an potentially real-time, currency conversion information
- The fact that the Currency and Bank classes are internal to the Money class, the user doesn't have to know about their implementation
- Ruby's dynamic nature allows for classes to be internal to Money, yet defined in separate files from the Money class, helping to keep the code modular from a developers point of view
- The addition of to_money() methods to basic Ruby primitives makes it easier to create money from just about any type
- Since Ruby supports arbitrarily large integer values, there is theoretically no upper bound to the amount of money that can be stored in a Money object
Disadvantages
- Doesn't support multiple currency types in a single object
- A Money object is immutable except in one way, ability to change currency type, which is an inconsistency that could easily have been avoided
- While the addition of to_money() methods to primitive types makes some things easier, it adds the additional ability to incorrectly combine an object of type Money with one that is not
- Since the Bank object is a singleton contained within the Money class, any modifications to currency exchange rates require locking of the Bank object which could result in performance degradation if exchange rates are modified often
python-money<ref name = pymoneywebsite /> (Python)
Overview
The python-money implementation of storing money in an object is a relatively simple library written in Python. As such, it does not have as much functionality nor the complexities that many other implementations have.
The python-money module consists of two classes, Money and Currency. Much like other implementations, the Money object contains an amount and currency internally. The amount is specified as type Decimal<ref name = pydecimal />, which allows storing the amount in a format similar to a float but without all of the precision issues that arise with using floats. When a Money object is created, either an identifier or Currency object is passed. Below is an example of the Currency data used for USD:
CURRENCY['USD'] = Currency(code='USD', numeric='840', name='US Dollar', countries=['AMERICAN SAMOA', 'BRITISH INDIAN OCEAN TERRITORY', 'ECUADOR', 'GUAM', 'MARSHALL ISLANDS', 'MICRONESIA', 'NORTHERN MARIANA ISLANDS', 'PALAU', 'PUERTO RICO', 'TIMOR-LESTE', 'TURKS AND CAICOS ISLANDS', 'UNITED STATES MINOR OUTLYING ISLANDS', 'VIRGIN ISLANDS (BRITISH)', 'VIRGIN ISLANDS (U.S.)'])
Within the namespace containing these classes, a default list of currency objects are created, indexed by their ISO4217<ref name = iso4217 /> standard three character and number identifiers. This is what allows specifying currency by identifier rather than currency object. Unfortunately this limits the currency types to those statically defined in the source code. Additionally, since there is not a separate class to handle conversions, the exchange rate information is contained within the Currency object. This limits the way that Money objects can be converted.
Advantages
- Much like an integer value in Ruby, a Decimal<ref name = pydecimal /> has a theoretically infinite precision and maximum value.
- As long as the user does not access the internal structure of the Money object directly, it behaves as an immutable object
Disadvantages
- This implementation does not support an arbitrary number of decimal points
- Since the currency conversion data is set within the Currency object itself
- There is no locking present for the Currency object and there could be race conditions when users are simultaneously setting and getting exchange rates
- The current implementation can only convert to and from the default currency type
- Since this implementation can take multiple types of arguments as the monetary value but does not add methods to primitives for converting them to Money objects, type checking has to be done on arguments to determine how to handle them. This is something that we want to avoid as part of object oriented design<ref name = skrien />.
Java
Overview
While Skrien introduces the Java Money class and then refines it through various stages to break it up into SimpleMoney and MixedMoney derived classes, there are various other implementations possible in Java itself. Some of the most common operations for money types are implemented in the <ref name = Currency />Currency class, which encapsulates, among other things, standard identifiers for currencies around the world. However, the lack of a standard Money Class in the Java library has resulted in users employing different schemes to conduct operations on money. One of the simplest ways to represent Money in Java programs is to use one of the following primitive types:
- float
- double
- int
- long
- BigDecimal
- Money Class (custom wrapper around BigDecimal)
Out of these, BigDecimal is the fundamental building block and custom Money Class implementations simply provide a higher level of abstraction around the BigDecimal type. This makes it a more practical form of use.
Here is an example snippet from [1] that shows the use of BigDecimal to perform some basic calculations on Money values <ref name = jsql/>.
import java.math.BigDecimal; import java.util.Currency; public class MoneyCalculation { private BigDecimal fAmountOne, fAmountTwo; private static int DECIMALS = 2; private static final BigDecimal TWO = new BigDecimal("2"); private static int ROUNDING_MODE = BigDecimal.ROUND_HALF_EVEN; private BigDecimal getSum(){ return fAmountOne.add(fAmountTwo); } private BigDecimal getAverage(){ return getSum().divide(TWO, ROUNDING_MODE); } }
One of the special cases of Java Money class implementation is found in Java Object-Relational Database Systems [2]. Objects that are defined in Java are stored in FirstSQL/J as values in database columns. These are normal columns whose type is a Java class that is mapped to the appropriate database type internally.
Money Class Constructors: Money(BigDecimal amt, String currency); Money(double amt, String currency); Note: currency is a string name of a currency – ‘USD’, ‘Euro’, … Methods: String getCurrency(); // get currency type string String toString(); // get amount with standard formatting BigDecimal decValue(); // get numeric amount double doubleValue(); // get numeric amount
Advantages
- The int and long primitive types are very simple to use while representing cents/pennies.
- The BigDecimal representation has built-in rounding modes.
- BigDecimal objects are immutable i.e. operations always return new objects, and do not modify the state of existing objects.
- The use of Currency class in custom Money implementations allows users to represent money in any currency. Conversions from one currency to another can be easily done with the use of a Currency object.
- An object oriented approach to Money allows encapsulation of methods such as adding, subtracting, negating money in one common implementation.
- The FirstSQL/J inbuilt querying capabilities provide significant elegant methods to get information about Money classes.
Disadvantages
- Depending on the number of digits (<=9, <=18, >18), the corresponding types int, long, BigDecimal respectively are recommended for representation. This requires the user to be aware of what values the representation needs to take up.
- Primitive types double and float carry small rounding differences.
- Significant amount of logic is required for custom implementation of Mixed Money - different currencies being available. Certain inefficiencies of code can creep in if the user is not careful with some of the implementation details.
C++
Overview
Representations similar to the ones in Java are also seen in C++. While the fundamentals are the same, there are a few subtle differences as well. There are certain functions like "floor" that can be used in C++ for aiding in the right precision with decimal values for money quantities. The underlying data type for such cases is double. Eg. floor(double*21.32). With this neat trick, decimals are not lost because the double type, with its precision being greater than or equal to 15 digits, is used as a compiler supported long long. The only thing to be careful of is multiplying and dividing such values.
The one cool feature is that the Zortech distribution has an in-built C++ Money Class in it.
class money { long dollars; int cents; // ... };
Advantages
- The Zortech C++ money class is an efficient implementation. This is due to the fact that the operators use integer arithmetic.
- Not much overhead with maintaining the money class objects.
- Some nice routines such as "flatten" are made available using the money class. An example of this implementation from [3] is shown here.
// Returns a money data item where the cents are // rounded modulo "cents". In this way cents can // be stripped of money items when the currency // does not have all the coins required to pay // every posible quantity. money flatten(const money& m, double cents, int rounding) { money temp; double c = floor(fabs(cents*money::SCALE())); // cents double r = fmod(m.m_money, c); // remainder temp.m_money = (!rounding || (2.0* r <= c) ? m.m_money - r : m.m_money - r + c ); return temp; }
Disadvantages
- The long datatype in the Zortech implementation cannot hold more than 10 decimal digits.
- This money class implements most arithmethic operators, but it does not implement the multiply and divide operations. The user is left to implement these.
References
<references> <ref name = skrien> Object Oriented Design Using Java / Dale Skrien -- 1st Ed. </ref> <ref name = moneygemwebsite> http://money.rubyforge.org/ </ref> <ref name = pymoneywebsite> http://code.google.com/p/python-money/ </ref> <ref name = pydecimal> http://docs.python.org/library/decimal.html </ref> <ref name = iso4217> http://www.abstracttechnology.com/standard/iso4217.html </ref> <ref name = Currency> http://docs.oracle.com/javase/1.4.2/docs/api/java/util/Currency.html </ref> <ref name = jsql> http://www.firstsql.com/javaobjects.shtml </ref> </references>