Decimal not rounding properly - c#

I have a decimal called 'sum' and its value is 5824088.999120m, but when I try to round it to 3 decimals I get 5824088.998m instead of 5824088.999m. It decrements instead of leaving 5824088.999m.
Why is it? Here's my code:
List<decimal> quantityList = QuantityList();
List<decimal> priceList = PriceList();
decimal destination = 5824088.999M;
decimal sum = 0M;
bool lifesaver = false;
for (int x = 0; x < priceList.Count; x++)
{
sum = 0M;
for (int i = 0; i < 3500; i++)
{
priceList[x] += 0.001M;
sum = 0M;
for (int y = 0; y < priceList.Count; y++)
{
decimal multipleProduct = priceList[y] * quantityList[y];
sum = sum + multipleProduct;
Console.WriteLine(priceList[y] + " " + quantityList[y]);
sum = Math.Round(sum, 3);
Console.WriteLine("Sum: " + sum);
Console.ReadKey();
Console.WriteLine();
}
if (sum == destination)
{
Console.WriteLine("The new number is " + priceList[x] + " and it is the {0} element!", x);
lifesaver = true;
break;
}
else if (sum > destination)
{
Console.WriteLine("Limit exceeded!");
}
if (i == 3499)
{
priceList[x] -= 3.500M;
}
if (lifesaver == true)
{
break;
}
}//Second for loop
if (lifesaver == true)
{
break;
}
}//Main for loop
The lists are in another method.

It seems that you have round up error accumulation and thus the total is wrong:
for (int y = 0; y < priceList.Count; y++) {
...
sum = Math.Round(sum, 3); // <- this accumulates round up errors
...
}
imagine that the priceList contains
priceList = new List<Decimal>() {
1.0004M, 1.0004M, 1.0004M};
while quantityList are all 1's; the sum will be
1.000M, 2.000M, 3.000M
while the actual total is
Math.Round(1.0004M + 1.0004M + 1.0004M, 3)
3.001M.
Possible remedy is not rounding up prematurely:
for (int y = 0; y < priceList.Count; y++) {
...
//DONE: comment out this: no premature rounding (within the loop)
// sum = Math.Round(sum, 3);
//DONE: but format out when printing out
Console.WriteLine("Sum: {0:F3}", sum);
...
}
// round up (if you want) after the loop
sum = Math.Round(sum, 3);

You should look into MidpointRounding (https://msdn.microsoft.com/en-us/library/system.midpointrounding(v=vs.110).aspx) and add this to your Math.Round function. Like this: sum = Math.Round(sum, 3, MidpointRounding.ToEven);, the other option you have is MidpointRounding.AwayFromZero, which might be better for you.

It is because of the Math.Round method.
Here is the MSDN documentation.
This method is equivalent to calling the Round(Decimal, Int32, MidpointRounding) method with a mode argument of MidpointRounding.ToEven.When d is exactly halfway between two rounded values, the result is the rounded value that has an even digit in the far right decimal position. For example, when rounded to two decimals, the value 2.345 becomes 2.34 and the value 2.355 becomes 2.36. This process is known as rounding toward even, or banker's rounding. It minimizes rounding errors that result from consistently rounding a midpoint value in a single direction.
You should try the Math.Round(decimal, int32, System.MidpointRounding), which can be found here
Try calling it with the MidPointRounding enum AwayFromZero value.
Example:
decimal d = 5824088.999120M;
decimal rounded = Math.Round(d, 3, System.MidpointRounding.AwayFromZero);
Console.WriteLine(rounded.ToString());
Logs 5824088.999
From remarks:
The decimals parameter specifies the number of significant decimal places in the return value and ranges from 0 to 28. If decimals is zero, an integer is returned.
In a midpoint value, the value after the least significant digit in the result is precisely half way between two numbers. For example, 3.47500 is a midpoint value if it is to be rounded two decimal places, and 7.500 is a midpoint value if it is to be rounded to an integer. In these cases, the nearest value can't be easily identified without a rounding convention, which is specified by the mode argument. The Round(Decimal, Int32, MidpointRounding) method supports two rounding conventions for handling midpoint values.
Rounding away from zero.
Midpoint values are rounded to the next number away from zero. For example, 3.75 rounds to 3.8, 3.85 rounds to 3.9, -3.75 rounds to -3.8, and -3.85 rounds to -3.9. This form of rounding is represented by the MidpointRounding.AwayFromZero enumeration member.
Rounding away from zero is the most widely known form of rounding.
Rounding to even, or banker's rounding
Midpoint values are rounded to the nearest even number. For example, both 3.75 and 3.85 round to 3.8, and both -3.75 and -3.85 round to -3.8. This form of rounding is represented by the MidpointRounding.ToEven enumeration member.
Rounding to nearest is the standard form of rounding used in financial and statistical operations. It conforms to IEEE Standard 754, section 4. When used in multiple rounding operations, it reduces the rounding error that is caused by consistently rounding midpoint values in a single direction. In some cases, this rounding error can be significant.
Check this .NET fiddle also. If you run it, you will see precisely your expected value 5824088.999

Related

Display very Large or Small Numbers in Scientific Notation by Counting the Zero's

I need a method to get the number of Zeros AFTER the Decimal point when the number BEFORE the decimal point is also Zero. So For example 0.00000000987654 would work out as 8, since there are 8 Zero's after 0. Turning the Decimal Data type into a string I could then display this in Scientific Notation as 9.87654E9.
The reason I need to do this is so I can iterate very small numbers multiple times producing results too high for calculators.
So for example 0.123456789 Multiplied by 0.1 and iterated a 1000 times. (0.123456789 * 0.1 * 0.1 * 0.1 * 0.1 ......) works out at 1.234567890000000000000000000E-1001 using the Decimal Data Type with the full 28-digit precision and displayed in Scientific Notation
I was able to achieve this when working with Factorials. For Example the Factorial of 1000 is 1000 x 999 * 998 * 997 * 996 .... all the way down to 0. This number is too high for calculators so I used iteration to achieve the result to 28-digit precision in Scientific Notation.
For the very large numbers I was successful. I achieved this by getting the number of Digits BEFORE the period:
static int Get_Digits_Before_Period(decimal Large_Number)
{
decimal d = decimal.Floor(Large_Number < 0 ? decimal.Negate(Large_Number) : Large_Number);
// 0.xyz should return 0, therefore a special case
if (d == 0m)
return 0;
int cnt = 1;
while ((d = decimal.Floor(d / 10m)) != 0m)
cnt++;
return cnt;
}
I now need a similar method but one for obtaining the number of Zero's AFTER the period.
The exponent range for decimal is 0 ~ -28, so it cannot represent a number such as 1.234567890000000000000000000E-1001, so I just explain numbers in the valid ranges.
To count the ZERO for a decimal, you can fetch the integer and exponent part of the decimal first
var number = 0.00000000987654m;
var bits = decimal.GetBits(number); //0~2 are integer part.
var exponent = (bits[3] & 0xff0000) >> 16;
Then reduce exponent by significant digits of the integers to get zero count after the period.
var zeros = exponent;
for(int i = 0; i <= 2; i++)
{
if(bits[i] != 0)
zeros -= (int)Math.Log10(bits[i]) + 1;
}
if(zeros < 0)
zeros = 0;

Double every time brings different values

It's my generating algorithm it's generating random double elements for the array which sum must be 1
public static double [] GenerateWithSumOfElementsIsOne(int elements)
{
double sum = 1;
double [] arr = new double [elements];
for (int i = 0; i < elements - 1; i++)
{
arr[i] = RandomHelper.GetRandomNumber(0, sum);
sum -= arr[i];
}
arr[elements - 1] = sum;
return arr;
}
And the method helper
public static double GetRandomNumber(double minimum, double maximum)
{
Random random = new Random();
return random.NextDouble() * (maximum - minimum) + minimum;
}
My test cases are:
[Test]
[TestCase(7)]
[TestCase(5)]
[TestCase(4)]
[TestCase(8)]
[TestCase(10)]
[TestCase(50)]
public void GenerateWithSumOfElementsIsOne(int num)
{
Assert.AreEqual(1, RandomArray.GenerateWithSumOfElementsIsOne(num).Sum());
}
And the thing is - when I'm testing it returns every time different value like this cases :
Expected: 1
But was: 0.99999999999999967d
Expected: 1
But was: 0.99999999999999989d
But in the next test, it passes sometimes all of them, sometimes not.
I know that troubles with rounding and ask for some help, dear experts :)
https://en.wikipedia.org/wiki/Floating-point_arithmetic
In computing, floating-point arithmetic is arithmetic using formulaic
representation of real numbers as an approximation so as to support a
trade-off between range and precision. For this reason, floating-point
computation is often found in systems which include very small and
very large real numbers, which require fast processing times. A number
is, in general, represented approximately to a fixed number of
significant digits (the significand) and scaled using an exponent in
some fixed base; the base for the scaling is normally two, ten, or
sixteen.
In short, this is what floats do, they dont hold every single value and do approximate. If you would like more precision try using a Decimal instead, or adding tolerance by an epsilon (an upper bound on the relative error due to rounding in floating point arithmetic)
var ratio = a / b;
var diff = Math.Abs(ratio - 1);
return diff <= epsilon;
Round up errors are frequent in case of floating point types (like Single and Double), e.g. let's compute an easy sum:
// 0.1 + 0.1 + ... + 0.1 = ? (100 times). Is it 0.1 * 100 == 10? No!
Console.WriteLine((Enumerable.Range(1, 100).Sum(i => 0.1)).ToString("R"));
Outcome:
9.99999999999998
That's why when comparing floatinfg point values with == or != add tolerance:
// We have at least 8 correct digits
// i.e. the asbolute value of the (round up) error is less than tolerance
Assert.IsTrue(Math.Abs(RandomArray.GenerateWithSumOfElementsIsOne(num).Sum() - 1.0) < 1e-8);

Round decimal value to nearest 5 or 0 amount

I have a web application that will apply a percentage markup to a product, but the percentage will be specified by a user. For example, the user can indicate they want to mark up a product 5%, 9%, 23%, etc. My problem is, the product price will change as well, and in doing so, end up giving ugly values ($1462.72)
As a result, my users are hoping that we can round the value to the nearest 5\0 value. So if my marked up product price is $1462.72, it would round up to $1465. $1798.02 on the other hand would round up to an even $1800.
Using VB\C#, how can I go about rounding these values?
Thanks!
To round to an arbitrary modulus you can create a function like:
public decimal Round(decimal source, decimal modulus)
{
return (Math.Round(source / modulus) * modulus);
}
and use it in this way:
decimal rounded = Round(1798.02m , 5.0m); // yields 1800.0
decimal rounded = Round(1462.72m , 5.0m); // yields 1465.0
decimal rounded = Round(2481.23m , 5.0m); // yields 2480.0
Note that Math.Round by default rounds midpoint values to the closest even number (e.g. 1.5 and 2.5 would both "round" to 2. In your case, the effect is that any numbers that are exactly between a 0 and 5 number (i.e. 2.5, 7.5) would be rounded to the closest 10:
decimal rounded = Round(1697.50m , 5.0m); // yields 1700.0
decimal rounded = Round(1702.50m , 5.0m); // yields 1700.0
If you want to always round UP on the midpoint just specify that in Round:
return (Math.Round(source / modulus, MidpointRounding.AwayFromZero) * modulus);
You can use the modulus operator to calculate the adjustment needed.
decimal price = 1798.02;
decimal adjustment = price % 5.0;
if(adjustment != 0) //so we don't round up already round numbers
{
price = (price - adjustment) + 5;
}
This will bring it up to the next multiple of 5.

Percentile algorithm

I am writing a program that finds percentile. According to eHow:
Start to calculate the percentile of your test score (as an example we’ll stick with your score of 87). The formula to use is L/N(100) = P where L is the number of tests with scores less than 87, N is the total number of test scores (here 150) and P is the percentile. Count up the total number of test scores that are less than 87. We’ll assume the number is 113. This gives us L = 113 and N = 150.
And so, according to the instructions, I wrote:
string[] n = Interaction.InputBox("Enter the data set. The numbers do not have to be sorted.").Split(',');
List<Single> x = new List<Single> { };
foreach (string i in n)
{
x.Add(Single.Parse(i));
}
x.Sort();
List<double> lowerThan = new List<double> { };
Single score = Single.Parse(Interaction.InputBox("Enter the number."));
uint length = (uint)x.Count;
foreach (Single index in x)
{
if (index > score)
{
lowerThan.Add(index);
}
}
uint lowerThanCount = (uint)lowerThan.Count();
double percentile = lowerThanCount / length * 100;
MessageBox.Show("" + percentile);
Yet the program always returns 0 as the percentile! What errors have I made?
Your calculation
double percentile = lowerThanCount / length * 100;
is all done in integers, since the right hand side consist of all integers. Atleast one of the operand should be of floating point type. So
double percentile = (float) lowerThanCount / length * 100;
This is effectively a rounding problem, lowerThanCount / length are both unit therefore don't support decimal places so any natural percentage calculation (e.g. 0.2/0.5) would result in 0.
For example, If we were to assume lowerThanCount = 10 and length = 20, the sum would look something like
double result = (10 / 20) * 100
Therefore results in
(10 / 20) = 0.5 * 100
As 0.5 cannot be represented as an integer the floating point is truncated which leaves you with 0, so the final calculation eventually becomes
0 * 100 = 0;
You can fix this by forcing the calculation to work with a floating point type instead e.g.
double percentile = (double)lowerThanCount / length * 100
In terms of readability, it probably makes better sense to go with the cast in the calculation given lowerThanCount & length won't ever naturally be floating point numbers.
Also, your code could be simplified a lot using LINQ
string[] n = Interaction.InputBox("Enter the data set. The numbers do not have to be sorted.")
.Split(',');
IList<Single> x = n.Select(n => Single.Parse(n))
.OrderBy(x => x);
Single score = Single.Parse(Interaction.InputBox("Enter the number."));
IList<Single> lowerThan = x.Where(s => s < score);
Single percentile = (Single)lowerThan.Count / x.Count;
MessageBox.Show(percentile.ToString("%"));
The problem is in the types that you used for your variables: in this expression
double percentile = lowerThanCount / length * 100;
// ^^^^^^^^^^^^^^^^^^^^^^^
// | | |
// This is integer division; since length > lowerThanCount, its result is zero
the division is done on integers, so the result is going to be zero.
Change the type of lowerThanCount to double to fix this problem:
double lowerThanCount = (double)lowerThan.Count();
You are using integer division instead of floating point division. Cast length/lowerThanCount to a float before dividing.
Besides the percentile calculation (should be with floats), I think your count is off here:
foreach (Single index in x)
{
if (index > score)
{
lowerThan.Add(index);
}
}
You go through indexes and if they are larger than score, you put them into lowerThan
Just a logical mistake?
EDIT: for the percentile problem, here is my fix:
double percentile = ((double)lowerThanCount / (double)length) * 100.0;
You might not need all the (double)'s there, but just to be safe...

Unexpected decimal value behavior

I used to think I understand the difference between decimal and double values, but now I'm not able to justify the behavior of this code snippet.
I need to divide the difference between two decimal numbers in some intervals, for example:
decimal minimum = 0.158;
decimal maximum = 64.0;
decimal delta = (maximum - minimum) / 6; // 10.640333333333333333333333333
Then I create the intervals in reverse order, but the first result is already unexpected:
for (int i = 5; i >= 0; i--)
{
Interval interval = new Interval(minimum + (delta * i), minimum + (delta * (i + 1));
}
{53.359666666666666666666666665, 63.999999999999999999999999998}
I would expect the maximum value to be exactly 64. What am I missing here?
Thank you very much!
EDIT: if I use double instead of decimal it seems to works properly!
You're not missing anything. This is the result of rounding the numbers multiple times internally, i.e. compounding loss of precision. The delta, to begin with, isn't exactly 10.640333333333333333333333333, but the 3s keep repeating endlessly, resulting in a loss of precision when you multiply or divide using this decimal.
Maybe you could do it like this instead:
for (decimal i = maximum; i >= delta; i -= delta)
{
Interval interval = new Interval(i - delta, i);
}
Double has 16 digits precision while Decimal has 29 digits precision. Thus, double is more than likely would round it off than decimal.

Categories

Resources