4

I want to handle prices in decimal currency (EUR) using C# without worrying about rounding errors and without rolling my own way of doing decimal arithmetic. The C# reference for the decimal type says

Compared to floating-point types, the decimal type has more precision and a smaller range, which makes it appropriate for financial and monetary calculations.

What?

I don't give a damn about precision, I only want about seven sig-figs. But I want to know that 5.31 EUR is an exact value. A, binary-coded-decimal type would be ideal. So my question is whether the C# decimal is that type.

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
Adrian Ratnapala
  • 5,485
  • 2
  • 29
  • 39
  • Don't get hung up on terms like 'precision'. `decimal` is designed for money calculations; you're in for a world of hurt if you use anything else. – Robert Harvey Apr 15 '14 at 17:34
  • That does not answer the question at all. If `decimal` actually uses decimals under the hood, then it works. But all the documentation I have seen carefully avoids saying this. – Adrian Ratnapala Apr 15 '14 at 17:35
  • 2
    The implementation is irrelevant. Knowing how it works is relevant, and is described in sufficient detail: it will work just fine for your use. – Tim S. Apr 15 '14 at 17:36
  • 1
    @TimS. The implementation matters because it an only possibly work if the internal representation is base 10. And until I saw Jon's answer, I couldn't find anything in the documentation to say that it could store "0.1" exactly. – Adrian Ratnapala Apr 15 '14 at 17:40
  • Try to keep your question clean; everyone can see Jon's answer below, and they can also see that you accepted it as the correct one. They can also see all of the comments. – Robert Harvey Apr 15 '14 at 17:49

1 Answers1

15

No, it's not BCD (where each digit is individually encoded in a specific number of bits) - but you don't want it to be. (And I certainly never claimed it was.)

decimal is a floating point type in that it has a significand and an exponent, both integers - it's just that unlike float and double, the "point" that gets shifted by the exponent is a decimal point rather than a binary point. It's unfortunate that MSDN says "compared with floating point types" when it really means "compared with binary floating point types".

The decimal documentation does actually make it reasonably clear though:

A decimal number is a floating-point value that consists of a sign, a numeric value where each digit in the value ranges from 0 to 9, and a scaling factor that indicates the position of a floating decimal point that separates the integral and fractional parts of the numeric value.

The binary representation of a Decimal value consists of a 1-bit sign, a 96-bit integer number, and a scaling factor used to divide the 96-bit integer and specify what portion of it is a decimal fraction. The scaling factor is implicitly the number 10, raised to an exponent ranging from 0 to 28. Therefore, the binary representation of a Decimal value the form, ((-296 to 296) / 10(0 to 28)), where -(296-1) is equal to MinValue, and 296-1 is equal to MaxValue. For more information about the binary representation of Decimal values and an example, see the Decimal(Int32[]) constructor and the GetBits method.

Community
  • 1
  • 1
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Isn't that still BCD? It is not fixed-point, it is just decimal scientific notation encoded as bits. – Adrian Ratnapala Apr 15 '14 at 17:36
  • 1
    @AdrianRatnapala: No, that's not BCD. Follow the link to see what BCD actually means. – Jon Skeet Apr 15 '14 at 17:37
  • So this works because any decimal integer can be accurately represented in base two, but the scaling factor is base 10? – Robert Harvey Apr 15 '14 at 17:41
  • I see. The exponent is decimal, but the mantissa in two's compliment binary. Which, is obviously the right way of doing things. – Adrian Ratnapala Apr 15 '14 at 17:42
  • I was going to say that, but I wasn't sure. :) – Robert Harvey Apr 15 '14 at 17:48
  • @RobertHarvey: I should clarify that any integer can be represented in any *integer* base. If you start using "base pi" or something like that, life becomes trickier ;) – Jon Skeet Apr 15 '14 at 17:53
  • @AdrianRatnapala: The right way of doing things would be to store the numbers in a fixed-point format, or else in base 1,000,000,000. The combination of decimal exponent and binary mantissa may seem elegant, but it makes addition, subtraction, and comparisons very expensive on numbers with different exponents. – supercat Aug 06 '14 at 22:41
  • @supercat: On the other hand, it gives you more flexibility. It's very hard to tick all the boxes, unfortunately :( Now if types could be parameterized by numbers, we could have `decimal<10>` or whatever which would fix the point to whatever the useful position would be in the context... that would relieve a lot of the issues, I suspect - at the cost of a more complicated type system :) – Jon Skeet Aug 06 '14 at 22:44
  • @JonSkeet: A 128-bit fixed-point type with 15 digits to the right of the decimal point would be able to accommodate an integer range larger than that of `unsigned long`, while having more precise numerical semantics than `Decimal` (e.g. one could guarantee that x+y-y will either yield x or overflow, something which is not guaranteed with `Decimal`). – supercat Aug 06 '14 at 23:05
  • @supercat: As it happens, that wouldn't meet my most recent use case, where I use decimal as a larger integer than long. But I agree that would be a fairly unusual use case. – Jon Skeet Aug 06 '14 at 23:07
  • @JonSkeet: An `int128` type could offer faster performance than `Decimal` as well as greater usefulness. Or, if one wanted the versatility of floating-point, one could use the bottom 30 bits of four words to store 9 decimal digits each, use four of the remaining bits to get a 37th decimal digit, three for an exponent (base 1billion) and one for sign. Every numerically-distinct `Decimal` could be represented, and many other values besides. Or, for better performance, don't bother with the 37th digit [it's needed to represent some `Decimal` values but otherwise probably isn't that useful]. – supercat Aug 06 '14 at 23:27
  • I think if I cared about performance, I would just cast to doubles and be done with. But I like your base 10^9 idea. Still I don't understand why people don't use one of the IEEE formats http://en.wikipedia.org/wiki/Decimal_floating_point#IEEE_754-2008_encoding – Adrian Ratnapala Aug 07 '14 at 06:07
  • @supercat: Yes, an `Int128` type would definitely be the right solution for this particular case, if it existed. – Jon Skeet Aug 07 '14 at 06:10
  • @AdrianRatnapala: Efficient computation with floating-point numbers requires that it be easy to multiply and divide the significand by the kind of unit used by the exponent. For binary floating-point, that means shifting. For base-ten floating-point using a binary significand, that would require dividing and multiplying the significant by powers of ten. Division by arbitrary powers of ten is much more expensive than shifting. – supercat Aug 07 '14 at 15:01