Use Integer Cents or Decimal for Accounting Amounts? - ruby-on-rails

I'm working on a rails application with Postgres and ActiveRecord that is keeping track of payments and transaction fees (which are based on percentages).
Currently I'm using BigDecimal (and decimal columns in ActiveRecord) to keep track of the values of these transactions, but dealing with rounding has been frustrating. (for example how 8.05 - 1.0 ==> 7.050000000000001)
Does it make sense to continue using decimal columns for dollar amounts? Or should I switch to storing everything in integer value cents so I don't need to deal with rounding issues.
An important note is that none of my transactions (as of now) are worth fractional cents.
What are the pros and cons of each approach?

There are a number of possible strategies for dealing with currency in Rails,
Such as What is the best method of handling currency/money?

Related

What is the recommended data type for handling Money (just 2-decimal places) in Elixir/Erlang?

What is the recommended data type for handling Money - numeric values with just 2-decimal places in Elixir/Erlang?
I think you should always use integers when handling money. Floating point operations can have rounding errors and money-handling code that's off even by 1 cent is often not ok. For example, instead of
amount = 99.99
Use
amount_cents = 9999
This is doubly important if you are storing the amount in a database since conversion between Elixir and your database and back may produce undesirable results.
I highly recommend using the Decimal library. There has been a lot of thought and work put into handling all the difficult edge cases.
Money, like cryptography, is not something you should implement yourself. You will get it wrong.
Using the Decimal library is the way to go in currency handling logic,
especially when you have to perform arithmetic operations with the quantities.

Common sense when storing currencies?

After reading up on how to best handle users in multiple timezones properly, I've learned that the way to go is to store all dates in an normalized, application-wide timezone - UTC and then apply the diff between the normalized timezone and the individual users timezone when outputting. Today I came to think if this would be appropriate to apply this approach to handling currency in software:
All stored currency are converted to a application-wide currency, lets say EUR (€), and when outputting, the currency is converted back into the users own currency, with an updated exchange rate of the day?
What's common sense here? How is this generally solved and what should I be aware of before choosing a way to handle this?
One standard approach is to store both an amount and a currency whenever monetary values are held and manipulated.
See the Money Pattern in Martin Fowler's Patterns of Enterprise Application Architecture.
Fowler describes defining a simple datatype to hold the two primitive components, with overloaded arithmetical operators for performing monetary operations:
"The basic idea is to have a Money class with fields for the numeric
amount and the currency. You can store the amount as either an
integral type or a fixed decimal type. The decimal type is easier for
some manipulations, the integral for others. You should absolutely
avoid any kind of floating point type, as that will introduce the kind
of rounding problems that Money is intended to avoid. Most of the time
people want monetary values rounded to the smallest complete unit,
such as cents in the dollar. However, there are times when fractional
units are needed. It’s important to make it clear what kind of money
you’re working with, especially in an application that uses both
kinds. It makes sense to have different types for the two cases as
they behave quite differently under arithmetic.
Money needs arithmetic operations so that you can use money objects as
easily as you use numbers. But arithmetic operations for money have
some important differences to money operations in numbers. Most
obviously, any addition or subtraction needs to be currency aware so
you can react if you try to add together monies of different
currencies. The simplest, and most common, response is to treat the
adding together of disparate currencies as an error. In some more
sophisticated situations you can use Ward Cunningham’s idea of a money
bag. This is an object that contains monies of multiple currencies
together in one object. This object can then participate in
calculations just like any money object. It can also be valued into a
currency."
The difference between handling time and currency, is that time zones doesn't shift in value.
When handling monetary values you have to consider what the currency of the actual money is. If the actual money is in USD and you store it as EUR, you will get a discrepancy between the actual value and the stored value when their values shift.
Alternatively you would have to recalculate all values when the exchange rate is updated, but that would take away the purpose of storing the values in a single currency.

In Delphi how do I determine when to use Real, Real48, Double or Single data types?

Most of my applications revolve around financial calculations involving payments and interest rate calculations. I'm looking to find out how to determine what Delphi data type is best to use.
If I'm using a database to store these values and I've defined the fields in that database to be a decimal value with two decimal places, which Delphi datatype is most compatible with that scenario?
Should I use a rounding formula in Delphi to format the results to two decimal places before storing the values in the database? If so what is a best practice for doing so?
For such calculations, don't use floating point types like Real, Single or Double. They are not good with decimal values like 0.01 or 1234.995, as they must approximate them.
You can use Currency, a fixed point type, but that is still limited to 4 decimal places.
Try my Decimal type, which has 28-29 places and has a decimal exponent so it is ideal for such calculations. The only disadvantage is that it is not FPU supported (but written in assembler, nevertheless) so it is not as fast as the built-in types. It is the same as the Decimal type used in .NET (but a little faster) and quite similar to the one used on the Mac.
If you want to do financial calculations, don't use any of the floating-point/real types. Delphi has a Currency type, which is a fixed-point value with 4 decimal places, that should be just what you need.

How should I present a cost field to the user, and store it in the database?

Right now I have two fields for cost. One for dollars and one for cents. This works, but it is a bit ugly. It also doesn't allow the user to enter the term "free" or "no cost" if they want. But if I only have one field, I might have to make my parser a bit smarter. What do you think?
On the server side, I combine dollars and cents to store them as decimals in my database. Mainly so that I can gather statistics (cost averages, etc.) quickly.
Do you think it is better to store the cost as a string? Then whenever I actually use the cost for stats or other purposes, I would convert it to a decimal at that point. Or am I on the right track?
There is a rule in database design that states that "atomic data" should not be split. By this rule a price, or cost is such an example of atomic data and therefore it should never be split among multiple columns just like you shouldn't split a phone number among multiple columns (unless you really have a very good reason for it - very rare)
Use a DECIMAL data type. Something like DECIMAL(8,3) should work and it's supported by all ANSI SQL compliant database products!
You can consult Joe Celko's "Thinking In Sets" book for a discussion of this topic. See section 1.6.2, pages 21-22.
EDIT -
It seems from your question that you are also concerned with how to accept user's input in a form that resembles the price (xxxx.xx) - hence the two input boxes, for the whole dollars, and the pennies.
I recommend using a single input box and then doing input validation using Regular Expressions to match your format (i.e. something like [0-9]+(.[0-9]{1,3})? would probably work but could be improved). You could then parse the validated string to a Decimal type in your language, or just pass it as a string into your database - SQL will know how to cast it to a DECIMAL type.
Keep the whole cost as decimal. If it's free, then keep the cost as 0. In presentation if cost is zero - write "free" instead of 0.
I generally store the cost as the lowest unit (pennies) and then convert it to whole dollars later.
So a cost of $4.50 gets stored as 450. Free items would be -1 pennies. You could store free things as 0 pennies as well, this gives you the flexibility to use 0 and -1 to mean two slightly different things (free vs no sale?).
It also makes it easier to support countries that don't use cents if you choose to go that route.
As for presenting the data entry field, I personally don't like it when I have to keep switching fields for tiny things (like when they break up phone numbers into 3 fields, or IP addresses into 4). I'd present one field, and let the users type the decimal point in themselves. That way, your users don't have to tab (or click, if they are unfamiliar with tab) to the next field.
Use cents, use 450 for $4.50 this will save you problems that are arising very often
from the fact that floating point operations are not safe. Just try the following expression in irb:
0.4 - 0.3 == 0.1 will return false. All because of floating point representation
innacuracies.
In my models I'm always using:
attr_accessor :price_with_cents
def price_with_cents
self.price/100.00
end
def price\_with\_cents==(num)
self.price = (num.to_f * 100.00).to_i
end
And the name of column is just price and integer type.
I don't have much experience with decimal columns and their representation in ruby (which can be float that is problematic as i've shown at the begining).
Don't allow garbage to make it to your database. If you're expecting a dollar amount on a field, than make sure it's valid before it gets in there. This will allow you to report better on the data and allow simpler formatting on output.
I suggest making this a single field with validation on update or insert.
if field != SpecialFreeTag then
try to convert to decimal
if fail then report to user
otherwise accept value
Use try parse or regular expressions to help with the validation.
I would store the cost as decimal with the scale being no less than 2 and maybe even 3-5. If something is bought in bulk the unit cost could easily include fractions of a cent. Free items have a cost of 0. If the cost is unknown then allow null values also.

Performance implications of using BigDecimal vs. Integer in a Rails app

How much less efficient would it be to store some fields as a BigDecimal instead of as an Integer in a Rails app?
Some computation (a bunch of arithmetic) will be done with these values.
Does this affect performance for Rails, the database or Ruby in general?
BigDecimal is less efficient than integer in most ways that matter. They take up more space, and floating-point math is slower than integer math.
Having said that, unless you're doing an awful lot of calculations, it's probably fine to use BigDecimal, and you probably won't notice.

Resources