.NET decimal rounding - weird result with a Digit repeating lots of times - c#

One of my unit tests failed today. I nailed it down to a rather peculiar decimal rounding issue.
var b = 196.5m;
var result1 = b / 12m; //16.375
var result2 = b * 1 / 12m; //16.375
var result3 = b * (1 / 12m); //16.374999999999999999999999993
What's happening there with result3?
I would very much prefer to use result3 code style (the actual code is much more complex than this obviously) but I want the result3 to be identical to result1 and result2.
-- UPDATE --
I have even rounded it:
Math.Round(196.5m * (1 / 12m), 2, MidpointRounding.AwayFromZero); //16.37
Math.Round(196.5m / 12m, 2, MidpointRounding.AwayFromZero); //16.38

This might be relevant, so I want to start by pointing out the Language Specification, section 4.1.7
If one of the operands of a binary operator is of type decimal, then the other operand must be of an integral type or of type decimal. If an integral type operand is present, it is converted to decimal before the operation is performed.
The result of an operation on values of type decimal is that which would result from calculating an exact result (preserving scale, as defined for each operator) and then rounding to fit the representation. Results are rounded to the nearest representable value, and, when a result is equally close to two representable values, to the value that has an even number in the least significant digit position (this is known as “banker’s rounding”). A zero result always has a sign of 0 and a scale of 0.
This tells you results3 and 4 in your test should give identical results. (Also note the "integral" word, implicit float/double conversion is not supported)
Now, in your case you've stumbled upon an equation with some nice simplification properties, in that 196.5 / 12 => 393 * (2/3) / 12 => 131 * 2 / 4. In your results3&4, you calculate the division by 3 first (1/12), which gives 0.0833..., something that can't be exactly represented in decimal. And then you scale 0.0833... up (your order of operations is to divide by 12 then multiple by b).
You can get the same result by first rounding a number that can't be represented in decimal, e.g., something with repeating digits, say 1/7m. For example, 917m * (1 / 7m) = 131.00000000000000000000000004 but note 917m/7m = 131.
You can mitigate this by preferring multiplications first (being careful of overflow). The other option is to round your results.
This is probably a dupe of something like Is C# Decimal Rounding Inconsistent?
or maybe
Rounding of decimal in c# seems wrong...

the problem lies in order of operations and precision of returned values
In your example the first does no multiplication, and the second does the multiplication first and then the division, the 3rd will first do the division, which will return 0,0833333333333333...
this will then be represented as 0,0833333333333333 which introduces an imprecision of 3,333...e-17*. this imprecision is then multiplied
When doing stuff like this, try to make sure to first do the multiplications and afterwards the divisions. Doing so will eliminate the risk of multiplying with imprecise fractions
-REMARK-* the exact number here is dependent on which type (decimal, float,...)is returned, which is in turn dependent on which types you are dividing, it likely is not 17
If you want to magnify this issue, try thinking this out in int

Related

How Can I Round 102.555 to 102.55?

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?
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.
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.
While string truncation will work, you want the floor() function used this way:
double value = Math.Floor(Math.Int(sourceVal * 100)) / 100;
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;
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"

Why does a division result differ based on the cast type?

Here's a part of code that I dont understand:
byte b1 = (byte)(64 / 0.8f); // b1 is 79
int b2 = (int)(64 / 0.8f); // b2 is 79
float fl = (64 / 0.8f); // fl is 80
Why are the first two calculations off by one? How should I perform this operation, so its fast and correct?
EDIT: I would need the result in byte
EDIT: Not entirely correct, see: Why does a division result differ based on the cast type? (Followup)
Rounding issue: By converting to byte / int, you are clipping of the decimal places.
But 64 / 0.8 should not result in any decimal places? Wrong: Due to the nature of floating point numbers, 0.8f can not be represented exactly like that in memory; it is stored as something close to 0.8f (but not exactly). See Floating point inaccuracy examples or similar threads. Thus, the result of the calculation is not 80.0f, but 79.xxx where xxx is close to 1 but still not exactly one.
You can verify this by typing the following into the Immediate Window in Visual Studio:
(64 / 0.8f)
80.0
(64 / 0.8f) - 80
-0.0000011920929
100 * 0.8f - 80
0.0000011920929
You can solve this by using rounding:
byte b1 = (byte)(64 / 0.8f + 0.5f);
int b2 = (int)(64 / 0.8f + 0.5f);
float fl = (64 / 0.8f);
I'm afraid fast and correct are at odds in cases like this.
Binary floating point arithmetic almost always creates small errors, due to the underlying representation in our CPU architectures. So in your initial expression you actually get a value a little bit smaller than the mathematically correct one. If you expect an integer as the result of a particular mathematic operation and you get something very close to it, you can use the Math.Round(Double, MidpointRounding) method to perform the correct rounding and compensate for small errors (and make sure you pick the MidpointRounding strategy you expect).
Simply casting the result to a type such as byte or int doesn't do rounding - it simply cuts off the fractional part (even 1.99999f will become 1 when you just cast it to these types).
Decimal floating point arithmetic is slower and more memory intensive, but doesn't cause these errors. To perform it, use decimal literals instead of float literals (e.g. 64 / 0.8m).
The rule of thumb is:
If you are dealing with exact quantities (typically man-made, like money), use decimal.
If you are dealing with inexact quantities (like fractional physical constants or irrational numbers like π), use double.
If you are dealing with inexact quantities (as above) and some accuracy can be further sacrificed for speed (like when working with graphics), use float.
To understand the problem, you need to understand the basics of floating point representation and operations.
0.8f can not be exactly represented in memory using a floating point number.
In mathematics, 64/0.8 equals 80.
In floating point arithmetics, 60/0.8 equals approximatively 80.
When you cast a float to an integer or a byte, only the integer part of the number is kept. In your case, the imprecise result of the floating point division is a little bit smaller than 80 hence the conversion to integer yields 79.
If you need an integer result, I would suggest you to round the result instead of casting it.
One way to do it is to use the following function, that convert to an integer by rounding to the closest integer :
Convert.ToInt32(64/0.8f);

Accumulating errors with decimal type?

I have an application where I accumulate decimal values (both adding and subtracting.) I use the decimal type rather than double in order to avoid accumulation errors. However, I've run into a case where the behavior is not quite what I'd expect.
I have x = a + b, where a = 487.5M and b = 433.33333333333333333333333335M.
Computing the addition, I get x = 920.8333333333333333333333334M.
I then have y = 967.8750000000000000000000001M.
I want to assert that y - x = y - a - b. However,
y - x = 47.0416666666666666666666667
y - a - b = 47.04166666666666666666666675
I thought this kind of error was exactly what the decimal type was intended to avoid, so what's happening here?
Here is code that reproduces the issue:
static void Main()
{
decimal a = 487.5M;
decimal b = 433.33333333333333333333333335M;
decimal x = a + b;
decimal y = 967.8750000000000000000000001M;
Console.WriteLine(y - x);
Console.WriteLine(y - a - b);
if (y - x != y - a - b)
Console.WriteLine("x - y != y - a - b");
Console.ReadKey();
}
There was some discussion in comments as to why these high precisions are necessary, so I thought I'd address in summary here. For display purposes, I certainly round the results of these operations, but I use decimal for all internal representations. Some of the computations take fractions along the way, which results in numbers that are beyond the precision of the decimal type.
I take care, however, to try and keep everything stable for accumulation. So, for instance, if I split up a quantity into three thirds, I take x/3, x/3 and then (x - x/3 - x/3). This is a system that is accounting for physical quantities that are often divided up like this, so I don't want to introduce biases by rounding too soon. For instance, if I rounded the above for x=1 to three decimals, I would wind up with 0.333, 0.333, 0.334 as the three portions of the operation.
There are real physical limitations to the precision of what the system can do, but the logical accounting of what it's trying to do should ideally stay as precise as it can. The main critical requirement is that the sum total quantity of the system should not change as a result of these various operations. In the above case, I'm finding that decimal can violate this assumption, so I want to understand better why this is happening and how I can fix it.
The C# type Decimal is not like the decimal types used in COBOL, which actually store the numbers one decimal digit per nibble, and uses mathematical methods similar to doing decimal math by hand. Rather, it is a floating point type that simply assumes quantities will not get so large, so it uses fewer bits for exponents, and uses the remaining the bits of 128 rather than 64 for double to allow for greatly increased accuracy.
But being a floating point representation, even very simply fractional values are not represented exactly: 0.1, for example, requires a binary repeating fraction and may not be stored as an exact value. (It is not, for a double; Decimal may handle that particular value differently, but this is true in general.)
Therefore comparisons still need to be made using typical floating point math procedures, in which values are compared, added, subtracted, etc., by accepting them only to a certain point. Since there are approximately 23 decimal places of accuracy, select 16 as your standard, for example, and ignore those at the end.
For a good reference, read What Every Computer Scientist Should Know About Floating Point Precision.
The Decimal type is a floating-point type which has more bits of precision than any of the other types that have been built into .NET from the beginning, and whose values are all concisely representable in base-10 format. It is, however, bulky and slow, and because it is a floating-point type it is no more able to satisfy axioms typical of "precise" types (e.g. for any X and Y, (X+Y-Y)==X should either return true or throw an overflow exception). I would guess that it was made a floating-point type rather than fixed-point because of indecision regarding the number of digits that should be to the right of the decimal. In practice, it might would have been faster, and just as useful, to have a 128-bit fixed-point format, but the Decimal type is what it is.
Incidentally, languages like PL/I work well with fixed-point types because they recognize that precision is a function of a storage location rather than a value. Unfortunately, .NET does not provide any nice means via which a variable could be defined as holding a Fixed(6,3) and automatically scale and shift a Fixed(5,2) which is stored into it. Having the precision be part of the value means that storing a value into a variable will change the number of digits that variables represents to the right of the decimal place.

Inconsistency in divide-by-zero behavior between different value types

Please consider the following code and comments:
Console.WriteLine(1 / 0); // will not compile, error: Division by constant zero
int i = 0;
Console.WriteLine(1 / i); // compiles, runs, throws: DivideByZeroException
double d = 0;
Console.WriteLine(1 / d); // compiles, runs, results in: Infinity
I can understand the compiler actively checking for division by zero constant and the DivideByZeroException at runtime but:
Why would using a double in a divide-by-zero return Infinity rather than throwing an exception? Is this by design or is it a bug?
Just for kicks, I did this in VB.NET as well, with "more consistent" results:
dim d as double = 0.0
Console.WriteLine(1 / d) ' compiles, runs, results in: Infinity
dim i as Integer = 0
Console.WriteLine(1 / i) ' compiles, runs, results in: Infinity
Console.WriteLine(1 / 0) ' compiles, runs, results in: Infinity
EDIT:
Based on kekekela's feedback I ran the following which resulted in infinity:
Console.WriteLine(1 / .0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001);
This test seems to corroborate the idea and a literal double of 0.0 is actually a very, very tiny fraction which will result in Infinity...
In a nutshell: the double type defines a value for infinity while the int type doesn't. So in the double case, the result of the calculation is a value that you can actually express in the given type since it's defined. In the int case, there is no value for infinity and thus no way to return an accurate result. Hence the exception.
VB.NET does things a little bit differently; integer division automatically results in a floating point value using the / operator. This is to allow developers to write, e.g., the expression 1 / 2, and have it evaluate to 0.5, which some would consider intuitive. If you want to see behavior consistent with C#, try this:
Console.WriteLine(1 \ 0)
Note the use of the integer division operator (\, not /) above. I believe you'll get an exception (or a compile error--not sure which).
Similarly, try this:
Dim x As Object = 1 / 0
Console.WriteLine(x.GetType())
The above code will output System.Double.
As for the point about imprecision, here's another way of looking at it. It isn't that the double type has no value for exactly zero (it does); rather, the double type is not meant to provide mathematically exact results in the first place. (Certain values can be represented exactly, yes. But calculations give no promise of accuracy.) After all, the value of the mathematical expression 1 / 0 is not defined (last I checked). But 1 / x approaches infinity as x approaches zero. So from this perspective if we cannot represent most fractions n / m exactly anyway, it makes sense to treat the x / 0 case as approximate and give the value it approaches--again, infinity is defined, at least.
A double is a floating point number and not an exact value, so what you are really dividing by from the compiler's viewpoint is something approaching zero, but not exactly zero.
This is by design because the double type complies with IEEE 754, the standard for floating-point arithmetic. Check out the documentation for Double.NegativeInfinity and Double.PositiveInfinity.
The value of this constant is the result of dividing a positive {or negative} number by zero.
Because the "numeric" floating point is nothing of the kind. Floating point operations:
are not associative
are not distributive
may not have a multiplicative inverse
(see http://www.cs.uiuc.edu/class/fa07/cs498mjg/notes/floating-point.pdf for some examples)
The floating point is a construct to solve a specific problem, and gets used all over when it shouldn't be. I think they're pretty awful, but that is subjective.
This likely has something to do with the fact that IEEE standard floating point and double-precision floating point numbers have a specified "infinity" value. .NET is just exposing something that already exists, at the hardware level.
See kekekela's answer for why this makes sense, logically.

Is dotNet decimal type vulnerable for the binary comparison error?

One error I stumble upon every few month is this one:
double x = 19.08;
double y = 2.01;
double result = 21.09;
if (x + y == result)
{
MessageBox.Show("x equals y");
}
else
{
MessageBox.Show("that shouldn't happen!"); // <-- this code fires
}
You would suppose the code to display "x equals y" but that's not the case.
The short explanation is that the decimal places are, represented as a binary digit, do not fit into double.
Example:
2.625 would look like:
10.101
because
1-------0-------1---------0----------1
1 * 2 + 0 * 1 + 1 * 0.5 + 0 * 0.25 + 1 * 0,125 = 2.65
And some values (like the result of 19.08 plus 2.01) cannot be be represented with the bits of a double.
One solution is to use a constant:
double x = 19.08;
double y = 2.01;
double result = 21.09;
double EPSILON = 10E-10;
if ( x + y - result < EPSILON )
{
MessageBox.Show("x equals y"); // <-- this code fires
}
else
{
MessageBox.Show("that shouldn't happen!");
}
If I use decimal instead of double in the first example, the result is "x equals y".
But I'm asking myself If this is because of "decimal" type is not vulnerable of this behaviour or it just works in this case because the values "fit" into 128 bit.
Maybe someone has a better solution than using a constant?
Btw. this is not a dotNet/C# problem, it happens in most programming languages I think.
Decimal will be accurate so long as you stay within values which are naturally decimals in an appropriate range. So if you just add and subtract, for example, without doing anything which would skew the range of digits required too much (adding a very very big number to a very very small number) you will end up with easily comparable results. Multiplication is likely to be okay too, but I suspect it's easier to get inaccuracies with it.
As soon as you start dividing, that's where the problems can come - particularly if you start dividing by numbers which include prime factors other than 2 or 5.
Bottom line: it's safe in certain situations, but you really need to have a good handle on exactly what operations you'll be performing.
Note that it's not the 128-bitness of decimal which is helping you here - it's the representation of numbers as floating decimal point values rather than floating binary point values. See my articles on .NET binary floating point and decimal floating point for more information.
System.Decimal is just a floating point number with a different base so, in theory, it is still vulnerable to the sort of error you point out. I think you just happened on a case where rounding doesn't happen. More information here.
Yes, the .NET System.Double structure is subject to the problem you describe.
from http://msdn.microsoft.com/en-us/library/system.double.epsilon.aspx:
Two apparently equivalent floating-point numbers might not compare equal because of differences in their least significant digits. For example, the C# expression, (double)1/3 == (double)0.33333, does not compare equal because the division operation on the left side has maximum precision while the constant on the right side is precise only to the specified digits. If you create a custom algorithm that determines whether two floating-point numbers can be considered equal, you must use a value that is greater than the Epsilon constant to establish the acceptable absolute margin of difference for the two values to be considered equal. (Typically, that margin of difference is many times greater than Epsilon.)

Categories