15

TL;DR1 What is an accurate and maintainable approach for representing currency or money in C?


Background to the question:
This has been answered for a number of other languages, but I could not find a solid answer for the C language.

Note: There's plenty more similar questions for other languages, I just pulled a few for representational purposes.

All of those questions can be distilled down to "use a decimal data type" where the specific type may vary based upon the language.

There is a related question that ends up suggesting using a "fixed point" approach, but none of the answers address using a specific data type in C.

Likewise, I have looked at arbitrary precision libraries such as GMP, but it's not clear to me if this is the best approach to use or not.


Simplifying Assumptions:

  • Presume an x86 or x64 based architecture, but please call out any assumptions that would impact a RISC based architecture such as a Power chip or an Arm chip.

  • Accuracy in calculations is the primary requirement. Ease of maintenance would be the next requirement. Speed of calculations is important, but is tertiary to the other requirements.

  • Calculations need to be able to safely support operations accurate to the mill as well as supporting values ranging up to the trillions (10^9)


Differences from other questions:

As noted above, this type of question has been asked before for multiple other languages. This question is different from the other questions for a couple of reasons.

Using the accepted answer from: Why not use Double or Float to represent currency?, let's highlight the differences.

(Solution 1) A solution that works in just about any language is to use integers instead, and count cents. For instance, 1025 would be $10.25. Several languages also have built-in types to deal with money. (Solution 2) Among others, Java has the BigDecimal class, and C# has the decimal type.

Emphasis added to highlight the two suggested solutions

The first solution is essentially a variant of the "fixed point" approach. There is a problem with this solution in that the suggested range (tracking cents) is insufficient for mill based calculations and significant information will be lost on rounding.

The other solution is to use a native decimal class which is not available within C.

Likewise, the answer doesn't consider other options such as creating a struct for handling these calculations or using an arbitrary precision library. Those are understandable differences as Java doesn't have structs and why consider a 3rd party library when there is native support within the language.

This question is different from that question and other related questions because C doesn't have the same level of native type support and has language features that the other languages don't. And I haven't seen any of the other questions address the multiple ways that this could be approached in C.


The Question:
From my research, it seems that float is not an appropriate data type to use to represent currency within a C program due to floating point error.

What should I use to represent money in C, and why is that approach better than other approaches?

1 This question started in a shorter form, but feedback received indicated the need to clarify the question.

Community
  • 1
  • 1
  • 4
    You can always work with cents and use `int`, `long`, or `long long`. – rpsml Aug 25 '15 at 19:18
  • You also don't want to reinvent the wheel because you'd easily end up with something broken and so slow. – edmz Aug 25 '15 at 19:23
  • 1
    i don't know if is the best approach, but i've seen several people using a struct formed by two integers one for the units and the other for decimals. – wallek876 Aug 25 '15 at 19:23
  • 3
    @Olaf - Thank you for that link. I had already reviewed it and I mention it in my question. That question focuses on Java which has different types available to it than what C does. –  Aug 25 '15 at 19:29
  • No it has not. Read the accepted answer! The default Java types are actually the same as for C, C++, Python, etc. They are the native IEEE754 types, possibly with slightly different settings, but that does not matter. The only difference would be arbitrary precision an true decimal floats as supported by some packages and special languages. But these are processed by the CPU and are very slow compared to the FPU types. Just use your reason: how do you represent e.g. 0.1 in a binary float representation? – too honest for this site Aug 25 '15 at 19:37
  • 1
    @Olaf - I believe you are referring to "A solution that works in just about any language is to use integers instead, and count cents. For instance, 1025 would be $10.25." That specific approach loses potentially significant information if you drop the tenths or hundredths, as mentioned below by ratchet freak. The other suggestion to use a `decimal` type doesn't exist in C. In addition, this question is looking beyond those suggestions to determine what the best course of action is given the 3 or 4 different ways this could be done. –  Aug 25 '15 at 19:45
  • Please point me where I refer to a fixed-point solution. "The other suggestion to use a decimal type doesn't exist in C" Tell me! Where do I write that C has built-in decimal float support? I clearly wrote that there are packages/libraries available. If not, you are free to write your own. And gues which types you will be using to implement them in C? SO is no discussion forum, but a Q&A site. You ask a **specific** question with a clear problem statement and we try to help you. – too honest for this site Aug 25 '15 at 19:56
  • If you don't see the common denominator for all the questions, independen of language, you really should first learn about fixed-size floats and their pitfalls. – too honest for this site Aug 25 '15 at 19:58
  • 1
    @Olaf - Again, thank you for the help. At this point, I don't think we're communicating clearly to each other. Please feel free to [ping me in chat](http://chat.stackexchange.com/rooms/21/the-whiteboard) so we can talk further outside of these comments here. –  Aug 25 '15 at 20:02

5 Answers5

10

Never use floating point for storing currency. Floating point numbers cannot represent tenths or hundredths, only diadic rationals, i.e. numbers of the form p/q where p and q are integers and q is a power of 2. Thus, any attempt to represent cents others than 0, 25, 50, or 75 cents will require an approximation, and these approximations translate into vulnerabilities that can be exploited to make you lose money.

Instead, store integer values in cents (or whatever the smallest division of the currency is). When reading values formatted with a radix point, simply read the whole currency units and cents as separate fields, then multiply by 100 (or the appropriate power of 10) and add.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • Should elaborate on the absolute, "Never use floating point for storing currency." – Fiddling Bits Nov 29 '15 at 05:05
  • 1
    The common binary floating point does not represent tenths or hundredths exactly. Decimal floating point does. Integer math has its issues with overflow and truncation vs. rounding. Using integer math for currently is fine for small programs using only `+,-,*`. Once code embarks on interest rates and mortgage calculations, the simplistic use of integers is inadequate. The weakness to this answer is that it ignores the many issues involved. – chux - Reinstate Monica Nov 29 '15 at 05:52
  • @chux: The C floating point types, on an implementation that conforms to Annex F, are binary floating point. Decimal floating point types would solve these problems, but they're not a standard language feature and way beyond the scope of OP's question, in my opinion. Things like interest calculations might or might not be in the scope of what the OP is interested in, but I'm giving an answer to the question, not writing a book on the topic. In any case, you still want/need to be using integers, but you just need additional techniques for applying interest rates, etc. – R.. GitHub STOP HELPING ICE Nov 29 '15 at 23:49
  • It is the "Never use floating point for storing currency" that overstates your simplified answer. Agree we do not need to write a book, yet sweeping directives like that do not acknowledge that larger issues exist that a programmer learner needs to be aware of. – chux - Reinstate Monica Nov 30 '15 at 00:01
  • @chux: There is no situation where a (binary) floating point type is suitable for storing currency amounts. The exponent is just wasted storage space because you need an exact integer value. There are no conditions or caveats to "never use floating point for storing currency"; the difficulty of some currency-related calculations does not invalidate this rule. So I'm not clear what point you're trying to make. – R.. GitHub STOP HELPING ICE Nov 30 '15 at 00:56
  • Excel for decades has been used by numerous companies to do financial calculations. Good or add, that binary floating point has been used heavily and has been found to be suitable enough. Perhaps we may chat for deeper discussion when we both have time. Thanks for the professional discourse. – chux - Reinstate Monica Nov 30 '15 at 02:32
7

The best money/currency representation is to use a higher enough precision floating point type like double that has FLT_RADIX == 10. These platforms/compliers are rare1 as the vast majority of systems have FLT_RADIX == 2.

Four alternatives: integers, non-decimal floating point, special decimal floating point, user defined structure.

Integers: A common solution uses the integer count of the smallest denomination in the currency of choice. Example counting US cents instead of dollars. The range of integers needs to be reasonable wide. Something like long long instead of int as int may only handle about +/- $320.00. This works fine for simple accounting tasks involving add/subtract/multiple but begins to crack with divisions and complex functions as used in interest calculations. Monthly payment formula. Signed integer math has no overflow protection. Care needs to be applied when rounding division results. q = (a + b/2)/b is not good enough.

Binary floating point: 2 common pitfalls: 1) using float which is so often of insufficient precision and 2) incorrect rounding. Using double well addresses problem #1 for many accounting limits. Yet code still often needs to use a round to the desired minimum currency unit for satisfactory results.

// Sample - does not properly meet nuanced corner cases.
double RoundToNearestCents(double dollar) {
  return round(dollar * 100.0)/100.0;
}

A variation on double is to use a double amount of the smallest unit (0.01 or 0.001). An important advantage is the ability to round simply by using the round() function which by itself meets corner cases.

Special decimal floating point Some systems provide a "decimal" type other than double that meets decimal64 or something similar. Although this handles most above issues, portability is sacrificed.1

User defined structure (like fixed-point) of course can solve everything except it is error prone to code so much and it is work (Instead). The result may function perfectly yet lack performance.

Conclusion This is a deep subject and each approach deserves a more expansive discussion. The general answer is: there is no general solution as all approaches have significant weaknesses. So it depends on the specifics of the application.

[Edit]
Given OP's additional edits, recommend using double number of the smallest unit of currency (example: $0.01 --> double money = 1.0;). At various points in code whenever an exact value is required, use round().

double interest_in_cents = round(
    Monthly_payment(0.07/12 /* percent */, N_payments, principal_in_cents));

[Edit 2021]
1C23 targeted to provide decimal based floating point. Wait until then?


My crystal ball says by 2022 the U.S. will drop the $0.01 and the smallest unit will be $0.05. I would use the approach that can best handle that shift.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • Perhaps your crystal ball also predicted Canada dropping the pennies, which happened in 2013. They round to the nearest 5c when dealing with cash (and only when dealing with cash). You still need to get the maths right though, so really nothing significant changes for calculations. – gilez Jan 10 '17 at 19:08
6

Either use an integer data type (long long, long, int) or a BCD (binary coded decimal) arithmetic library. You should store tenths or hundredths of the smallest amount you will display. That is, if you are using US dollars and presenting cents (hundredths of a dollar), your numeric values should be integers representing mills or millrays (tenths or hundredths of a cent). The extra significant figures will ensure your interest and similar calculations round consistently.

If you use an integer type, make sure that its range is great enough to handle the amounts of concern.

mpez0
  • 2,815
  • 17
  • 12
5

If speed is your primary concern, then use an integral type scaled to the smallest unit you need to represent (such as a mill, which is 0.001 dollars or 0.1 cents). Thus, 123456 represents $123.456.

The problem with this approach is that you may run out of digits; a 32-bit unsigned int can represent something like 10 decimal digits, so the largest value you could represent under this scheme would be $9,999,999.999. Not good if you need to deal with values in the billions.

Another approach is to use a struct type with one integral member to represent the whole dollar amount, and another integral member to represent the fractional dollar amount (again, scaled to the smallest unit you need to represent, whether it's cents, mills, or something smaller), similar to the timeval struct that saves whole seconds in one field and nanoseconds in the other:

struct money {
  long whole_dollars; // long long if you have it and you need it
  int frac_dollar; 
};                          

An int is more than wide enough to handle the scaling any sane person would use. Leaving it signed in case the whole_dollars portion is 0.

If you're more worried about storing arbitrarily large values, there's always BCD, which can represent way more digits than any native integral or floating-point type.

Representation's only half the battle, though; you also have to be able to perform arithmetic on these types, and operations on currency may have very specific rounding rules. So you'll want to keep that in consideration when deciding on your representation.

John Bode
  • 119,563
  • 19
  • 122
  • 198
  • `unsigned` is more appropriate than `signed`, no? – Fiddling Bits Aug 25 '15 at 19:59
  • 1
    @FiddlingBits: You need to be able to represent negative values, so you need a sign somewhere. Either used signed types for the members (`frac_dollar` needs to be signed in case `whole_dollars` is `0`), or make them both unsigned and use a third member for sign. – John Bode Aug 25 '15 at 20:25
4

int (32 or 64 as you need) and think in cents or partial cents as needed. With 32 bit and thinking in cents you can represent up to 40 million dollar in a single value. With 64 bit it's well beyond all the US dept ever combined.

There are some gotchas when doing the calculations you have to be aware of so you don't divide away half the significant numbers.

It's a game of knowing the ranges and when the rounding after division is fine.

For example doing a proper round (of the .5 up variaty) after division can be done by first adding half the numerator to the value and then doing the division. Though if you are doing finance you will need a bit more advanced round system though approved by your accountants.

long long res = (amount * interest + 500)/1000;

Only convert to dollar (or whatever) when communicating to the user.

ratchet freak
  • 47,288
  • 5
  • 68
  • 106