user2912902
user2912902

Reputation: 337

Round decimals and convert to % in C#

I have a list of probabilities like

0.0442857142857143
0.664642857142857
0.291071428571429

I want to convert them to the nearest percentages so that the sum of percentages adds up to 100

so something like this

0.0442857142857143 - 4 %
0.664642857142857  -  67 %
0.291071428571429  -  29 %

I cannot rely on Math.Round to always give me results which will add up to 1. What would be the best way to do this?

Upvotes: 2

Views: 2422

Answers (5)

Alexandre Rondeau
Alexandre Rondeau

Reputation: 2687

This is an method that could do the job.

public int[] Round(params decimal[] values)
{
    decimal total = values.Sum();
    var percents = values.Select(x=> Math.Round(x/total*100)).ToArray();
    int totalPercent = perents.Sum();
    var diff = 100 - totalPercent;
    percents[percents.Lenght - 1] += diff;
    return percents;
}

Upvotes: 3

Sid
Sid

Reputation: 317

Logic is , Firstly we have to round off the "after decimal value" then apply round off to whole value.

  static long PercentageOut(double value)
    {
        value = value * 100;
        value = Math.Round(value, 1, MidpointRounding.AwayFromZero); // Rounds "up"
        value = Math.Round(value, 0, MidpointRounding.AwayFromZero); // Rounds to even

        return Convert.ToInt64(value);
    }


    static void Main(string[] args)
    {


        double d1 = 0.0442857142857143;
        double d2 = 0.664642857142857;
        double d3 = 0.291071428571429;

        long l1 = PercentageOut(d1);
        long l2 = PercentageOut(d2);
        long l3 = PercentageOut(d3);

        Console.WriteLine(l1);
        Console.WriteLine(l2);
        Console.WriteLine(l3);
     }

Output

   4
   67
   29
   ---
sum is 100 %

Upvotes: 0

Corey
Corey

Reputation: 16564

Interesting collection of answers.

The problem here is that you are getting a cumulative error in your rounding operations. In some cases the accumulated error cancels out - some values round up, others down, cancelling the total error. In other cases such as the one you have here, the rounding errors are all negative, giving an accumulated total error of (approximately) -1.

The only way to work around this in the general case is to keep track of the total accumulated error and add/subtract when that error gets large enough. It's tedious, but the only real way to get this right:

static int[] ToIntPercents(double[] values)
{
    int[] results = new int[values.Length];
    double error = 0;
    for (int i = 0; i < values.Length; i++)
    {
        double val = values[i] * 100;
        int percent = (int)Math.Round(val + error);
        error += val - percent;
        if (Math.Abs(error) >= 0.5)
        {
            int sign = Math.Sign(error);
            percent += sign;
            error -= sign;
        }
        results[i] = percent;
    }

    return results;
}

This code produces reasonable results for any size array with a sum of approximately +1.0000 (or close enough). Array can contain negative and positive values, just as long as the sum is close enough to +1.0000 to introduce no gross errors.

The code accumulates the rounding errors and when the total error exceeds the acceptable range of -0.5 < error < +0.5 it adjusts the output. Using this method the the output array for your numbers would be: [4, 67, 29]. You could change the acceptable error range to be 0 <= error < 1, giving the output [4, 66, 30], but this causes odd results when the array contains negative numbers. If that's your preference, change the if statement in the middle of the method to read:

if (error < 0 || error >= 1)

Upvotes: 2

Pierre-Luc Pineault
Pierre-Luc Pineault

Reputation: 9191

Since you don't seem to care which number is bumped, I'll use the last. The algo is pretty simple, and will works for both the .4 edge case where you must add 1 and the one at .5 where you must remove 1 :

1) Round each number but the last one 2) Subtract 100 from the sum you have 3) Assign the remainder to the last number

As an extension method, it looks like this :

public static int[] SplitIntoPercentage(this double[] input)
{
    int[] results = new int[input.Length];
    for (int i = 0; i < input.Length - 1; i++)
    {
        results[i] = (int)Math.Round(input[i] * 100, MidpointRounding.AwayFromZero);
    }

    results[input.Length - 1] = 100 - results.Sum();

    return results;
}

And here's the associated unit tests :

    [TestMethod]
    public void IfSumIsUnder100ItShouldBeBumpedToIt()
    {
        double[] input = new []
            {
                0.044,
                0.664,
                0.294
            };

        var result = input.SplitIntoPercentage();

        Assert.AreEqual(100, result.Sum());
        Assert.AreEqual(4, result[0]);
        Assert.AreEqual(66, result[1]);
        Assert.AreEqual(30, result[2]);
    }

    [TestMethod]
    public void IfSumIsOver100ItShouldBeReducedToIt()
    {
        double[] input = new[]
            {
                0.045,
                0.665,
                0.295
            };

        var result = input.SplitIntoPercentage();

        Assert.AreEqual(100, result.Sum());
        Assert.AreEqual(5, result[0]);
        Assert.AreEqual(67, result[1]);
        Assert.AreEqual(28, result[2]);
    }

Once refactored a little bit, the result looks like this :

public static int[] SplitIntoPercentage(this double[] input)
{
    int[] results = RoundEachValueButTheLast(input);
    results = SetTheLastValueAsTheRemainder(input, results);

    return results;
}

private static int[] RoundEachValueButTheLast(double[] input)
{
    int[] results = new int[input.Length];
    for (int i = 0; i < input.Length - 1; i++)
    {
        results[i] = (int)Math.Round(input[i]*100, MidpointRounding.AwayFromZero);
    }

    return results;
}

private static int[] SetTheLastValueAsTheRemainder(double[] input, int[] results)
{
    results[input.Length - 1] = 100 - results.Sum();

    return results;
}

Upvotes: 0

Dylan Corriveau
Dylan Corriveau

Reputation: 2557

You could just multiply the number by 100 (if you have the decimal number)

0.0442857142857143 * 100 = 4 %
0.664642857142857 * 100 = 66 %
0.291071428571429 * 100 = 29 %

E: correct, 0.291071428571429 wouldn't add up to 30%...

Upvotes: 1

Related Questions