2

I'm testing a system which gave 10% of discount in a product, this means 102.555 of discount, but the system only use 2 fractional digits, so it gave 102.55.

The problem is if I execute this:

Math.Round(102.555, 2, MidpointRounding.AwayFromZero)

The output is: 102.56

And if I execute this:

Math.Round(102.555, 2, MidpointRounding.ToEven)

The output is also 102.56.

I was using Math.Round method with all scenarios, until this came up. What am I doing wrong?

Why is 102.555 returning 102.56 with MidpointRounding.AwayFromZero?

How Can I do something to return 102.55 from 102.555?

alansiqueira27
  • 8,129
  • 15
  • 67
  • 111
  • 10
    What you want isn't rounding, it's truncation – James Thorpe Jan 17 '17 at 16:41
  • 2
    See : http://stackoverflow.com/questions/13522095/rounding-down-to-2-decimal-places-in-c-sharp – PaulF Jan 17 '17 at 16:43
  • Possible duplicate of [Rounding down to 2 decimal places in c#](http://stackoverflow.com/q/13522095/4934172) – 41686d6564 stands w. Palestine Jan 17 '17 at 16:43
  • 3
    Possible duplicate of [Rounding down to 2 decimal places in c#](http://stackoverflow.com/questions/13522095/rounding-down-to-2-decimal-places-in-c-sharp) – PaulF Jan 17 '17 at 16:43
  • How is that rounded? 102.555 rounded IS 102.56. I don't follow your logic. – jdmdevdotnet Jan 17 '17 at 16:44
  • @JamesThorpe, then how can I truncate to 2 fractional digits? – alansiqueira27 Jan 17 '17 at 16:45
  • @JamesThorpe: Interestingly, [Merriam Webster](https://www.merriam-webster.com/dictionary/round) disagrees: "Definition of round / transitive verb (...) to express as a round number —often used with off <11.3572 rounded off to two decimal places becomes 11.36>" – O. R. Mapper Jan 17 '17 at 16:46
  • 2
    @O.R.Mapper Yes, that's been properly rounded. As have the examples provided by the OP in the question. But changing `0.555` to `0.55` is not rounding it, it's truncating it. – James Thorpe Jan 17 '17 at 16:47
  • I've learned from a similar situation that looking at the final number is pointless: start from the initial values you have, and step through *every single calculation*, examine *every single number* you are touching, both in and out of the calculations, to find out where's the pitfall (I know it was tricky for us to overcome the issue we were facing) – Alex Jan 17 '17 at 16:47
  • Note that if you _truncate_, then 102.5599 will also be converted to 102.55. I think you need to add more context to your question to get the right answer, otherwise everyone is guessing. – D Stanley Jan 17 '17 at 16:47
  • I guess you want to round the value to the nearest _0.05_ fraction. Try this : `Math.Round(d1 * 20) / 20;` – Pikoh Jan 17 '17 at 16:48
  • 1
    @AlGoreRhythm: There are different [tie-breaking conventions](https://en.wikipedia.org/wiki/Rounding#Tie-breaking). – O. R. Mapper Jan 17 '17 at 16:49
  • 1
    @JamesThorpe: By applying the [round half down tie-breaking convention](https://en.wikipedia.org/wiki/Rounding#Round_half_down), `0.555` can be rounded to `0.55`. – O. R. Mapper Jan 17 '17 at 16:49
  • @O.R.Mapper Fair enough. Not sure I'd personally use anything other than either of the built-in `MidpointRounding` options though, suspect it'd lead to other corner cases elsewhere! – James Thorpe Jan 17 '17 at 16:54
  • 1
    this is not a mystery. Like @Mapper said, it seems like OP here is talking about **rounding half toward zero** or perhaps **rounding half down**. These are not supported by .Net directly, but can be implemented manually if needed. – SlimsGhost Jan 17 '17 at 16:59
  • @Seva: Can you confirm what you would expect the following to be rounded to: 102.554, 102.556 . This would help clarify a lot of questions people seem to have on the exact requirements you have. There are many functions that might return 102.55 from 102.555 but they might not necessarily do what you want with *other* numbers. – Chris Jan 17 '17 at 17:38

5 Answers5

3

Why is 102.555 returning 102.556 with MidpointRounding.AwayFromZero?

I presume "102.556" should read "102.56" here.

Because MidpointRounding indicates what happens when you are rounding a number where the significant digit for rounding (i.e. the last disappearing digit) at radix 10 is 5. This is the case for your number 102.555, so when rounding to two decimal places, there are two options:

  • 102.55
  • 102.56

MidpointRounding.AwayFromZero picks the option that is further away from zero - in this case (as these are positive numbers) the greater one, namely 102.56.

O. R. Mapper
  • 20,083
  • 9
  • 69
  • 114
  • 2
    upvoted - worth linking [to the docs](https://msdn.microsoft.com/en-us/library/system.midpointrounding(v=vs.110).aspx) too, has a fairly indepth explanation of what's going on with both options – James Thorpe Jan 17 '17 at 16:58
  • ok but, I want to return 102.55, and MidpointRounding.ToEven doesn't return it. I want to know "How Can I Round 102.555 to 102.55?" – alansiqueira27 Jan 17 '17 at 17:19
  • It doesn't answer his actual question. – Dispersia Jan 17 '17 at 18:13
  • @Dispersia: Oh, how nice. Please check out the edit history of the question. This answer *did* answer the actual question, before the OP edited the question to ask something different. In fact, when I wrote this answer before that edit, this answer was *the only* one that answered the actual question. – O. R. Mapper Jan 17 '17 at 18:16
  • 1
    Really? Because I can clearly read the title that has not changed of `How Can I Round 102.555 to 102.55?` – Dispersia Jan 17 '17 at 18:17
  • 1
    @Dispersia: I answered the question that was stated as the last thing in the body. – O. R. Mapper Jan 17 '17 at 18:20
  • 2
    @Seva: You should not expect `ToEven` to round to an odd number. – Eric Lippert Jan 17 '17 at 19:06
2

Ok, I found the answer:

https://stackoverflow.com/a/13483693/375422

public double TruncateDown(double number, int decimalPlaces)
{
    return Math.Floor(number * Math.Pow(10, decimalPlaces)) / Math.Pow(10, decimalPlaces);
}

public double TruncateUp(double number, int decimalPlaces)
{
    return Math.Ceiling(number * Math.Pow(10, decimalPlaces)) / Math.Pow(10, decimalPlaces);
}

In my case, I want to round down.

Community
  • 1
  • 1
alansiqueira27
  • 8,129
  • 15
  • 67
  • 111
  • 1
    This does not "round down". It _truncates_ to 2 decimal points. I note again that 102.559999 would get truncated to 102.55 as well. If that's your intent that's fine, but I would name your functions appropriately (`TruncateDown` and `TruncateUp` is what I would call them). – D Stanley Jan 17 '17 at 17:57
  • 1
    This looks horribly inefficient. Any math gurus know why there is no MidpointRounding.TowardZero? – Dispersia Jan 17 '17 at 18:19
0

While string truncation will work, you want the floor() function used this way:

double value = Math.Floor(Math.Int(sourceVal * 100)) / 100;

Antonio Ciolino
  • 546
  • 5
  • 16
0

There are two types of rounding, the banker's rounding (i.e. to even), and everyday rounding (i.e. away from zero).

Simply put, away from zero rounding simply checks the number before the precision specified, and if it is 5 or greater, then it rounds up.

However, to even checks whether it will approach an even number when it rounds up, and if so, then it rounds up. However, it it approaches an odd number, then it won't round up.

Bear in mind, the default method Math.Round(x, y) uses to even implicitly.

To even gives a complimentary correct calculation. See here. 1.5 + 2.5 = 4. If you round each one and sum them up, you will still get 4, however, you will get calculation error if you do the same with away from zero.

var x = Math.Round(1.5, 0) + Math.Round(2.5, 0); // 4
var y = Math.Round(1.5, 0, MidpointRounding.AwayFromZero) + Math.Round(2.5, 0, MidpointRounding.AwayFromZero); // 5!

See here for more info: https://msdn.microsoft.com/en-us/library/system.midpointrounding(v=vs.110).aspx

Edit 1:

Following O. R. Mapper & sgmoore comments, I just realized that the point of bankers rounding is to have the odds of rounding up or down almost equally the same over the course of random numbers.

In away from zero rounding, you end up with 1-4 (four odds) rounding down, and 5-9 rounding up (5 odds).

However, in bankers rounding, 1-4 (four odds) will round down, and 6-9 will round up (4 odds), and then we have 5 that will either round up or down, and when applied to random numbers, the odds of rounding up is almost like the odds of rounding down. And that gives a better chance for more accurate calculation specially when summing up.

You can find out more here: Why does .NET use banker's rounding as default?

Edit 2:

If you want to truncate, you can simply use:

decimal x = 102.555M;
decimal truncated = Math.Truncate(x * 100) / 100; // 102.55;
Community
  • 1
  • 1
Ghasan غسان
  • 5,577
  • 4
  • 33
  • 44
  • "1.5 + 2.5 = 4" - indeed, but 1.5 + 1.5 = 2.5 + 2.5 = 4, or am I missing something? – O. R. Mapper Jan 17 '17 at 17:01
  • To even will make 1.5 => 2, and 2.5 => 2, which will give the correct result. However, away from zero will make it like 1.5 => 2, 2.5 => 3, and so 2 + 3 = 5! – Ghasan غسان Jan 17 '17 at 17:04
  • 1
    "To even will make 1.5 => 2, and 2.5 => 2, which will give the correct result." - I understand that, but it also means that 1.5 + 1.5 gets rounded to 2 + 2 = 4, and so does 2.5 + 2.5, even though these should yield 3 and 5, respectively. Thus, in such cases, it seems to even will yield an incorrect result, doesn't it? – O. R. Mapper Jan 17 '17 at 17:07
  • How Can I do something to return 102.55 from 102.555? – alansiqueira27 Jan 17 '17 at 17:27
  • 1
    @O.R.Mapper If you are rounding, then you can't expect correct results all the time. No matter what method you use, you can also find some set of numbers that produce incorrect results. Bankers rounding is to avoid the problem where you have a lot of random but discrete values (like 0.1, 0.2, 0.3 etc) and rounding up will round down 4 out of 10 times but round up 5 out of 10 times (and remain unchanged the tenth time) and so is slightly less accurate than if you round up and down the same number of times. (Bankers rounding also avoids the problem with negative numbers). – sgmoore Jan 17 '17 at 18:01
  • Thanks all, I just realized that the point of banker's rounding is about having equal odds of rounding up & down, and that it also does not have positive / negative bias. I have updated my answer accordingly. – Ghasan غسان Jan 18 '17 at 02:23
0

There is a great system to round to the nearest integer adding half to the number. For example if you want to round 3.8 to 4, you first add 0.5 (it will bring it to 4.3) and then cut the 0 (using mod or int).

In your sample, you need to add 0.005 and then cut the last digit. It can be done with toFixed(2) to keep two digits after the dot. Here you have some console output...

(102.555+.005).toFixed(2)
"102.56"
(102.555).toFixed(2)
"102.56"

toFixed rounds to the nearest number so in this case you don't need to add half but to substract it.

(102.555-.005).toFixed(2)
"102.55"