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.
Related
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.
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?
I'm playing around with rails a bit and I have found something strange. For storing a money value I use the typical decimal data type which active record converts to BigDecimal. I considered this to be precise and I thought to avoid the odd behavior of floating point math. But when I store 99.99 to the db everything works fine, but when the records gets loaded by active record it loses precision and converts to something like 99.9899999999. This looks like a floating point issue.
I made some tests and found out that creating a BigDecimal like this b = BigDecimal.new("99.99") leads to a "clean" variable but building it this way b = BigDecimal.new(99.99) leads to the "unclean" version that I want to avoid.
I guess, that ActiveRecord reconstructs the BigDecimal with an intermediate float when loading the record from the database. This is not what I want and I would like to know if it can be avoided.
Ruby Version 1.9.3p0
Rails 3.2.9
Sqlite 3.7.9
Your problem is that you're using SQLite and SQLite doesn't have native support for numeric(m,n) data types. From the fine manual:
1.0 Storage Classes and Datatypes
Each value stored in an SQLite database (or manipulated by the database engine) has one of the following storage classes:
NULL. The value is a NULL value.
INTEGER. The value is a signed integer, stored in 1, 2, 3, 4, 6, or 8 bytes depending on the magnitude of the value.
REAL. The value is a floating point value, stored as an 8-byte IEEE floating point number.
TEXT. The value is a text string, stored using the database encoding (UTF-8, UTF-16BE or UTF-16LE).
BLOB. The value is a blob of data, stored exactly as it was input.
Read further down that page to see how SQLite's type system works.
Your 99.99 may be BigDecimal.new('99.99') in your Ruby code but it is almost certainly the REAL value 99.99 (i.e. an eight byte IEEE floating point value) inside SQLite and there goes the neighborhood.
So switch to a better database in your development environment; in particular, develop on top of whatever database you're going to be deploying on.
Don't use floating point for monetary values
Yes, exactly, SQLite is messing up your BigDecimal values.
The fundamental problem is that the FP format cannot store most decimal fractions correctly.
I believe you have about four choices:
Round everything to, say, two decimal places so that you never notice the slightly-off values.
Store your BigDecimal values in SQLite with the TEXT or BLOB storage class.
Use a different db that has some sort of decimal string support.
Scale everything to integral values and use the INTEGER storage class.
The problem is that FP fractions are rational numbers of the form x/2n. But the decimal monetary amounts have fractions that are x/(2n * 5m). The representations just aren't compatible. For example, in 0.01 ... 0.99, only 0.25, 0.50, and 0.75 have exact binary representations.
NSString seems like the safest bet, but also the laziest. I don't know much about core date internals, so i'm not exactly sure what the performance benefits, if any, using indexed integer attribute over a indexed NSString attribute.
Assumptions about performance are bad. Without proof, assumptions are worth nothing.
The database engine may well compare strings faster than int32 if it is implemented well, and string indexes also have the potential to be faster than int32 indexes. So not assume int32 will be faster overall.
Start with the easiest solution. Easiest means less bugs. Laziness is good.
Then use the profiler to see what eats CPU cycles and work on that. If the string-based lookups are an issue, try with int32 ids. Or the other way, whatever. The important word here is profiler.
I wonder what the idiomatic/builtin/fastest way is to do this: I get numbers as a string of characters, they look like:
0012345
or
001234p
Strings that end with letters represent negative values. The scale is available separately.
Depending on the scale info, the first could be what we'd normally write as 1234.5, .0012345, 12,345.00, etc. And the second is -12,340 or -1,234.0 ... or -.001234. "p" is 0, "q" is 1 & so on.
My first thought is pedestrian what-my-mom would-do string jiggering and Decimal.Parse.
I have to parse tens of thousands of such numbers at startup of an interactive app - so if there's a faster way to do it that's great. (Though I don't yet know for a fact that performance is ever a problem) I suppose there must in theory be a faster way to do it by writing a different decimal parse that recognizes the numbers as alternatives to digits. In practice the negative numbers are a tiny minority of the numbers I'll be seeing, but faster is better.
Thanks,
Levin
Until you know that performance is a problem, code this in the simplest way possible. Not worth optimizing such code ahead of time, it'll almost certainly perform well however you handle this.