I Am Not Myself

Bills.Pay(Developer.Skills).ShouldBeTrue()

In This Post We Remember You Can Do Math With Computers

I recently ran into an interesting problem. I’ll try to describe the problem with out going into specific details of the domain the problem lives in. Let’s say I have a list of elements and each element have a property called weight that contains a decimal that represents that elements percentage in the entire list. If you sum the weights it should add up to 1M or 100%. This list of elements was loaded up from a text file and the text file weight had a precision of nine meaning that the number was represented with nine numbers after the decimal, 0.000000000. With this list I need to modify the precision to six and still have the sum of the weights add up to 100% exactly.

Here is what I came up with.

public class WeightRounder
    {
        private const int SIGNIFIGANT_DIGITS = 6;

        public IList<element> RoundOff(IList<element> elements)
        {
            if (elements.Count > 0)
            {
                MakeRoundedElements(elements);
                RedistributeWeightError(elements, GetTotalWeightError(elements));
            }
            return model;
        }

        private static void MakeRoundedModel(IEnumerable<element> elements)
        {
            model.Each(x => x.UpstreamWeight = Math.Round(x.Weight, SIGNIFIGANT_DIGITS));
        }

        private void RedistributeWeightError(IEnumerable<element> elements, decimal totalWeightError)
        {
            int errorSign = Math.Sign(totalWeightError);
            decimal step = (decimal) Math.Pow(10, -SIGNIFIGANT_DIGITS)*errorSign;
             
            elements.OrderByDescending(x => x.UpstreamWeight)
                    .TakeWhile(x => Math.Abs(totalWeightError) > decimal.Zero)
                    .Each(x =>
                          {
                              x.UpstreamWeight += step;
                              totalWeightError -= step;
                          });

            //indicates the elements were nowhere near 100% to begin with.
            if (totalWeightError != 0)
                throw new ApplicationException("Rounding failed. Total weight error {0} was to large to handle.".FormatWith(totalWeightError));
        }

        private decimal GetTotalWeightError(IEnumerable<element> elements)
        {
            var totalWeightError = decimal.One;
            elements.Each(x => totalWeightError -= x.UpstreamWeight);
            return totalWeightError;
        }
    }

Thoughts, comments or rants on my general approach and math skills appreciated.

2 responses to “In This Post We Remember You Can Do Math With Computers

  1. dirk June 5, 2011 at 8:21 am

    Boy does that look scary familiar. One thing I’ve found kind of interesting is that if you ask enough people about how to round something, eventually you’ll find the guy that actually knows math and can explain the pros and cons of different methods for redistributing error. In the above case, I think the idea is to overweight the larger weights, where the additional weight is not as significant. Not that it really matters, but a more robust, better way to do this might be to try to even out the statistical significance of each “error point”. I.e., if you had .9997 and .0001 as weights, adding one point to .9997 and then one point to .0001 results in the second weight being 50% larger when it would have been better to give the extra weight to the first weight where it hardly matters.

    Woh…I think I need a drink now.

  2. Bobby Johnson June 5, 2011 at 9:09 am

    it should look familiar, i pretty much ripped it right out of fusion. After staring at it for a while to understand it.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: