10

I do not know why Ceiling behave like in the below image

Why is processingFee != Settings.PaymentProcessingFeeInPercentage * prizesSum ?

View image at full size

alt text http://img514.imageshack.us/img514/3950/csharpceilingproblem.png

Sorin
  • 2,258
  • 4
  • 27
  • 45
  • I didnt understand your question, but be sure that Math.Ceiling() wants a parameter decimal or double – Serkan Hekimoglu Jul 01 '10 at 14:23
  • What is the problem here? processingFee is equals to 131, which seem ok. – Pierre-Alain Vigeant Jul 01 '10 at 14:25
  • @Serkan - Math.Ceiling accepts both decimal and double. – djdd87 Jul 01 '10 at 14:26
  • @Pierre - The error is the result in processingFee should be 130, not 131. – djdd87 Jul 01 '10 at 14:26
  • 3
    LOL@self. I tried to scroll to the right. – maxwellb Jul 01 '10 at 14:28
  • k now I see the usual floating point problem that everybody should know about. This question is then a duplicate of many other before it – Pierre-Alain Vigeant Jul 01 '10 at 14:29
  • possible duplicate of [Why is floating point arithmetic in C# imprecise?](http://stackoverflow.com/questions/753948/why-is-floating-point-arithmetic-in-c-imprecise) – Pierre-Alain Vigeant Jul 01 '10 at 14:31
  • Tip: When you encounter a problem like this, you should ask your question with the starting assumption that it is not a bug (i.e. ask "why doesn't this behave like I expect?" rather than, "is this broken?"). This is particularly true when you're not doing anything particularly weird and are using a common namespace. – Brian Jul 01 '10 at 14:49

7 Answers7

25

Your percentage isn't actually 0.05. It's a value close to 0.05... and probably a little bit more than 0.05. Thus when it's multiplied by 2600, you're getting a value just over 130.0... which is then being "ceilinged" to 131.0.

Using a little tool I wrote a while ago (available from this page about .NET binary floating point types) it looks like the actual float value closest to 0.05 is 0.0500000007450580596923828125. For doubles, it's 0.05000000000000000277555756156289135105907917022705078125.

Moral of the story: don't use float for this sort of thing - use decimal. Or if you're just trying to represent a percentage, if it's okay to actually be only accurate to one percent, use an integer value 0-100.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 1
    @pixel3cs: No, don't use `double`, either. It has the same problems as `float`. The reason `decimal` avoids these issues is because it is a floating point base 10 number instead of a floating point base 2 number. In general, `decimal` is going to be your safest choice for working with money, because the internal representation of `decimal` matches the thing you are trying to represent (though less efficiently). – Brian Jul 01 '10 at 14:52
4

This is a result of the floating point representation of the numbers involved. See the wikipedia. Probably 0.05 has an infinite base 2 representation as a double, so the value Math.Ceiling actually sees might be slightly larger than 130.

Community
  • 1
  • 1
Jens
  • 25,229
  • 9
  • 75
  • 117
  • To be absolutely annoyingly precise: a small (mathematical) integer such as 130 has an accurate finite representation in IEEE floating point numbers (either 32 bit or 64 bit). What may not be representable in floating point numbers is the (mathematical) real number close to 130 that OP thinks is equal to 130 – High Performance Mark Jul 01 '10 at 14:35
2

You're seeing floatng-point imprecision.
The actual base-2 representation of 0.05 is a tiny bit more than 0.05, so the product is a tiny bit more than 130.0.
Therefore, Math.Ceiling is rounding up.

Change your floats and doubles to decimal.

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
1

This is due to the internal storage format for a floating point number being inherently inexact when the number is represented in decimal. There are many, many questions on this on Stack Overflow.

The number you are returning is probably something like 130.000000000000001 since the numbers in your calculation can't be represented exactly as a binary floating point number.

David M
  • 71,481
  • 13
  • 158
  • 186
1

IMHO, it's probably something to do with floating point precision. In other words, 2600 × 0.05 gives 130.0000...001 rather than 130.

What if you try to round the result first, than call Math.Ceiling?

Arseni Mourzenko
  • 50,338
  • 35
  • 112
  • 199
0

In my compiler, when i lookup the multiply value, it says 130.00000193715096, so the math.ceiling result is ok. The problem is the limited precision of the float data type.

Try using 'double' instead, if it is possible.

Diego Pereyra
  • 417
  • 3
  • 10
  • @maxwellb: Not for the value of Settings.PaymentProcessingFeeInPercentage. However, `double` would still give the "wrong" answer. It's simply inappropriate to use `float` or `double` here. – Jon Skeet Jul 01 '10 at 14:28
  • Ah yes, thank you. And yes on the second point, too. What I meant was "double is no more appropraite than float". – maxwellb Jul 01 '10 at 14:30
  • double in this case gives the expected result, anyway, we know the best option is to analyze wich precision is really needed for that data. – Diego Pereyra Jul 01 '10 at 14:33
  • Problem is not limited precision of the float data type, it is lack of understanding of how to use floating point arithmetic. SO is covered in such lack of understanding. – High Performance Mark Jul 01 '10 at 14:37
  • I agree, that's why I said that an analysis is necessary. You can even find out that you can do the necessary arithmetic with integers without losing precision (and it can even be faster). – Diego Pereyra Jul 01 '10 at 15:04
0

If you use floating point numbers in a large banking operation, don't let me float my money in your bank. Use decimals, or integers of the least common denominator, i.e. cents.

You could however, use a Math.Round, to help you use doubles or floats, if you make assumptions about how large your calculations will get. i.e.:

double processingFee = Math.Ceiling( Math.Round( 
    Settings.PaymentProcessingFeeInPercentage * prizesSum, 2 ) );
maxwellb
  • 13,366
  • 2
  • 25
  • 35
  • 1
    Note that `decimal` in C# is still a floating point type. It's just a floating *decimal* point rather than a floating *binary* point. – Jon Skeet Jul 01 '10 at 14:29
  • Still "valid" only to a certain number of sigfigs. Good distinction. – maxwellb Jul 01 '10 at 14:37