0

I just did a test with LINQPad:

Could you explain me why/how the ceiling method is reacting like this? Notice the 123.12 in the middle.

Math.Ceiling(123.121 * 100) / 100 'display 123.13
Math.Ceiling(123.1200000000001 * 100) / 100 'display 123.13
Math.Ceiling(123.12000000000001 * 100) / 100 'display 123.12
Math.Ceiling(123.12000000000002 * 100) / 100 'display 123.13

I did the test in VB.NET but it should be the same in C#.

Matt Wilko
  • 26,994
  • 10
  • 93
  • 143
Bastien Vandamme
  • 17,659
  • 30
  • 118
  • 200
  • 1
    some form of precision loss – Andrey Nov 26 '15 at 11:18
  • 8
    It's due to the fact that floating point values are inherently imprecise. Please see eg http://stackoverflow.com/questions/753948/why-is-floating-point-arithmetic-in-c-sharp-imprecise – David Arno Nov 26 '15 at 11:19
  • That could possibly relate to binary fractions (http://floating-point-gui.de/formats/binary/). Since float are represented by two integers in a division, not every float can be used. That is especially the case when you have a lot of zeros behind the decimal separator followed by 1 (like in your examples). In that case the flow get rounded. For example 123.12000000000001 might be rounded to 123.12 before it gets passed to the ceiling method. – Sascha Nov 26 '15 at 11:21
  • Bear in mind that with `123.0000000001` you are defining a `Double` variable. You can increase the accuracy up to certain level (say... from around 10 decimal positions to the maximum `Decimal` value) by relying on `Decimal` type. You can use it by writing `123.0000000001D`. In any case note that this is only relevant in extreme cases (over 10 decimal positions is not something too common; not even ideal/recommendable). – varocarbas Nov 26 '15 at 11:28
  • 2
    @DavidArno Your statement might be easily misunderstood by some people. A couple of days ago I saw an answerer here defending that the fact of using `Double` or `Decimal` was actually relevant while dealing with a number including only 4 decimal digits, what is certainly not. A more correct version of your statement would be: "floating point values are inherently imprecise, for a high enough number of decimal digits". – varocarbas Nov 26 '15 at 11:33
  • 1
    @varocarbas. Fair point, well made. – David Arno Nov 26 '15 at 11:34
  • @varocarbas Since `double` can't represent even `0.1` exactly and `Ceiling` is a discontinuous function, imprecisions can matter, even with only one decimal digit. For example `Math.Ceiling(12 * 0.1 * 10)` is 13 on my machine. – CodesInChaos Nov 26 '15 at 15:43
  • @CodesInChaos Although you are making a pretty fair point, it is not exactly what my comments (and the original question) was about. When you perform an operation like multiplication, the exact number of decimal digits might not be as clear as it seems. In the case you are proposing, for example: `Dim testVal As Double = 12.0 * 0.1` is not 1.2, but 1.2000000000000002 (notably above my 10 digits recommendation). – varocarbas Nov 26 '15 at 15:52
  • @varocarbas The concept of decimal digits isn't useful when talking about double, since it can only represent power-of-two denominators exactly, so almost all doubles will need many decimal digits if you want to represent them exactly. If you want an example closer to the OP's question, consider `Math.Ceiling(0.07 * 100)`, which is 8 on my machine. – CodesInChaos Nov 26 '15 at 16:17
  • @CodesInChaos Sorry if I wasn't clear enough. My first comment was a (kind of) continuation to a discussion I had a couple of days ago regarding rounding certain values (e.g., 1.234). One of the answerers there defended that the fact of being `Double` or `Decimal` did have some effect. This is where my point of number of decimal digits has any meaning; in cases like what the OP is proposing (just assigning a value of x length to a given `Decimal`/`Double` variable) or even by rounding a known `Decimal`/`Double` variable. My point was: don't be afraid of `Double` by default... – varocarbas Nov 26 '15 at 16:21
  • ...`Decimal` is clearly more precise and, consequently, if you are worried about accurate enough decimal results and the size of the values is below the maximum for `Decimal`, you should definetively use this type. But you don't need to be unnecessarily scared of `Double` because of thinking that might provoke unexpected wrong results. The clearer is the picture, the more adequate will be the decisions made on account of such a knowledge; don't you think? Also 10 digits is a rough estimation (= many decimal digits). I cannot think of many situations seriously requiring more than 4 decimals. – varocarbas Nov 26 '15 at 16:23
  • PS: `Dim test0 As Double = 0.07 * 100` equals 7.0000000000000009. – varocarbas Nov 26 '15 at 16:30

4 Answers4

2

This is floating point rounding. C# parses 123.12000000000001 and 123.12 as having the same value. 123.12000000000002 is parsed as the next available double.

 var bytes = BitConverter.ToString(BitConverter.GetBytes(123.12));
 // outputs 48-E1-7A-14-AE-C7-5E-40
 var bytes1 = BitConverter.ToString(BitConverter.GetBytes(123.12000000000001));
 // outputs 48-E1-7A-14-AE-C7-5E-40
 var bytes2 = BitConverter.ToString(BitConverter.GetBytes(123.12000000000002));
 // outputs 49-E1-7A-14-AE-C7-5E-40
edeboursetty
  • 5,669
  • 2
  • 40
  • 67
0

This is due to floating point rounding rather than Math.Ceiling per se which is because floating point values cannot represent all values with 100% accuracy.

Your example is a little contrived anyway because if you try to type 123.12000000000001 in visual studio is changes it to 123.12 because it knows that the value cannot be represented as a double.

Read up on this here: What Every Computer Scientist Should Know About Floating-Point Arithmetic (btw this is not specific to .NET)

To fix your issue you can use a decimal value instead of a double. Math.Ceiling has an overload which accepts a decimal (all of these display 123.13):

   Debug.WriteLine(Math.Ceiling(123.121D * 100) / 100) 
   Debug.WriteLine(Math.Ceiling(123.1200000000001D * 100) / 100)
   Debug.WriteLine(Math.Ceiling(123.12000000000001D * 100) / 100) 
   Debug.WriteLine(Math.Ceiling(123.12000000000002D * 100) / 100) 

Whether this fix is appropriate of course depends on what level of accuracy you require.

Matt Wilko
  • 26,994
  • 10
  • 93
  • 143
0

Ceiling returns the number passed to it if they are whole numbers, or else the next highest whole number. So 5.0 stays 5.0 but 5.00001 becomes 6.0.

So, of the examples, the following are obvious:

Math.Ceiling(123.121 * 100) / 100 // Obtain 12312.1, next highest is 12313.0, then divide by 100 is 123.13
Math.Ceiling(123.1200000000001 * 100) / 100 // Likewise
Math.Ceiling(123.12000000000002 * 100) / 100 // Likewise

The more confusing one is:

Math.Ceiling(123.12000000000001 * 100) / 100 //display 123.12

However, let's take a look at:

123.12000000000001 * 100 - 12312.0 // returns 0

Compared to:

123.1200000000001 * 100 - 12312.0 // returns 1.09139364212751E-11
123.12000000000002 * 100 - 12312.0 // returns 1.81898940354586E-12

The latter two multiplications have results that are slightly higher than 12312.0, so while (123.12000000000002 * 100).ToString() returns "12312" the actual number produced by 123.12000000000002 * 100 is mathematically 12312.000000000002 the nearest possible double for 123.12000000000002 is is 123.1200000000000181898940354586 so that is what is worked on.

If you are used to only doing decimal arithmetic it may seem strange that 123.12000000000002 is "rounded" to 123.1200000000000181898940354586, but remember that these numbers are stored in terms of binary values, and rounding depends on the base you are working in.

So while the string representation doesn't indicate it, it is indeed slightly higher than 12312 and so its ceiling is 12313.

Meanwhile with 123.12000000000001 * 100, that is mathematically 12312.000000000001 but the nearest possible double to 123.12000000000001 is that it can fit into is 123.12. So that is what is used for the multiplication, and when the result passed to the subsequent call to Ceiling() its result is 12312.

Jon Hanna
  • 110,372
  • 10
  • 146
  • 251
-1

The Ceiling method returns the next higher integer equivalent.

so Ceiling(123.01) = 124

& Ceiling(123.0) = 123

Shudipto
  • 61
  • 1
  • 5