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.