5

Given 2 values like so:

decimal a = 0.15m;
decimal b = 0.85m;

Where a + b will always be 1.0m, both values are only specified to 2 decimal places and both values are >= 0.0m and <= 1.0m

Is it guaranteed that x == total will always be true, for all possible Decimal values of x, a and b? Using the calculation below:

decimal x = 105.99m;
decimal total = (x * a) + (x * b);

Or are there cases where x == total only to 2 decimal places, but not beyond that?

Would it make any difference if a and b could be specified to unlimited decimal places (as much as Decimal allows), but as long as a + b = 1.0m still holds?

Matt Warren
  • 10,279
  • 7
  • 48
  • 63
  • What do you mean *specified to unlimited decimal places*? Is `0.85m` not the same as `0.8500000000000000m` ? – Rotem Jan 03 '13 at 13:43
  • @Rotem The binary representation is different; I'm not sure whether or not it leads to behavioural differences when you multiply though. Edit: [Apparently not](http://msdn.microsoft.com/en-us/library/system.decimal.aspx) - `Trailing zeroes do not affect the value of a Decimal number in arithmetic or comparison operations` – Rawling Jan 03 '13 at 13:47
  • @Rawling This answer which quotes MSDN claims otherwise: http://stackoverflow.com/a/1132777/860585 – Rotem Jan 03 '13 at 13:48
  • I guess I meant if more than the first 2 decimal places were not 0, i.e. `a = 0.0001` and `b = 0.9999` for example. – Matt Warren Jan 03 '13 at 13:49
  • @Rotem: `However, trailing zeroes can be revealed by the ToString method if an appropriate format string is applied.` - the underlying structure knows how many leading zeroes are there, although it doesn't lead to different behaviour arithmetically. – Rawling Jan 03 '13 at 13:50
  • I would still avoid trailing zeroes as much as possible as they are still essentially taking away possible precision. `0.1` is `1e-1` while `0.100000` is `100000e-6`. – poke Jan 03 '13 at 13:58
  • I don't expect there to be trailing zeros and in reality a and b will only be specified with up to 4 decimal places. I just wanted to see if the # of decimal places made a difference, because if it does I can constrain it. – Matt Warren Jan 03 '13 at 14:02
  • Trailing zeros don't matter. Decimals are stored like doubles but have longer mantissa and shorter exponent. – CubeSchrauber Jan 03 '13 at 14:04
  • @CubeSchrauber No, that’s not true. Decimals are stored with base 10, not base 2 as floating point numbers are. – poke Jan 03 '13 at 14:09
  • @poke look at this [documentation](http://msdn.microsoft.com/en-us/library/system.decimal.getbits.aspx) – CubeSchrauber Jan 03 '13 at 14:14
  • 1
    @CubeSchrauber Did you look at it? Check the example output for `1` and `1.0000000000000000000000000000` at the bottom. Decimals are not stored like doubles/floats, but use base 10 which makes a huge difference. – poke Jan 03 '13 at 14:18
  • @poke No look at the number 4294967295 in the examples there. It's clearly a binary number 'FFFFFFFF'. Only the scaling factor (exponent) is meant as power of 10 but even this exponent is represented as a binary number – CubeSchrauber Jan 03 '13 at 14:37
  • 1
    @CubeSchrauber Are you serious?! Of course it’s stored in a binary form, because everything is (btw. `FF` is hexadecimal, not binary). That does not change the fact how floats/doubles and decimals are internally represented. Floats are represented using `(-1)^s * m * 2^e`; that’s why it can represent 0.5 exactly (`1 * 2^-1`) but not 0.3. Decimals on the other hand are represented with base 10, using `(-1)^s * m * 10^e`, so we can represent all decimals *exactly*, including 0.3 (`3 * 10^-1`), if m and e are precise enough. Read on [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point). – poke Jan 03 '13 at 15:26

2 Answers2

5

Decimal is stored as a sign, an integer, and an integer exponent for the number 10 that represents the decimal location. So long as your integral portion of the number (e.g. 105 in 105.99) is not sufficiently large, then a + b will always equal one. and the outcome of your equation (x * a) + (x * b) will always have the correct value for the four decimal places.

Unlike float and double, precision is not lost up to the size of the data type (128 bits)

From MSDN:

The Decimal value type represents decimal numbers ranging from positive 79,228,162,514,264,337,593,543,950,335 to negative 79,228,162,514,264,337,593,543,950,335. The Decimal value type is appropriate for financial calculations requiring large numbers of significant integral and fractional digits and no round-off errors. The Decimal type does not eliminate the need for rounding. Rather, it minimizes errors due to rounding. For example, the following code produces a result of 0.9999999999999999999999999999 rather than 1

decimal dividend = Decimal.One;
decimal divisor = 3;
// The following displays 0.9999999999999999999999999999 to the console
Console.WriteLine(dividend/divisor * divisor);
Charles Lambert
  • 5,042
  • 26
  • 47
3

The maximum precision of decimal in the CLR is 29 significant digits. When you're using that kind of precision, you're really talking approximation especially if you do multiplication because that requires intermediate results that the CLR must be able to process (see also http://msdn.microsoft.com/en-us/library/364x0z75.aspx).

If you have x with 2 significant digits and, say, a with 20 significant digits, then x * a will already have a minimum precision of 22 digits, and possibly more may be needed for intermediate results.

If x always has only 2 significant digits and you can keep the number of significant digits in a and b low enough (say, 22 digits -- pretty good and probably far enough away from 27 to deal with rounding errors), then I suppose (x * a) + (x * b) should be a pretty precise calculation always.

Finally, whether a + b always makes up 1.0m is of no significance related to a and b's individual precisions.

Roy Dictus
  • 32,551
  • 8
  • 60
  • 76