Потеря десятичной точности при попытке вычислить дроби коробки - PullRequest
3 голосов
/ 03 марта 2012

У меня есть сценарий, в котором у меня есть стандартная коробка, которая содержит 3 банки.Для отображения и запроса я должен сообщить в виде десятичной суммы стандартной конфигурации. Невозможно сказать 1 коробку из 3 банок, 1 коробку из 2 банок ... и т.д.

Например, сначала у меня будет 1 коробка из 3 банок
Я затем удаляю 1 банку, в результате чего 0,66 повторяющаяся коробка из 3 банок
Iзатем удалите еще 1 банку, в результате чего 0,33 повторяющегося ящика из 3 банок
I, а затем удалите окончательную банку, в результате чего 0,0000000000000000000000000001 коробку из 3 банок

При удалениипоследняя банка, которую я хотел бы, чтобы значение было 0 коробок из 3 банок , поскольку каждая банка теперь удалена из оригинальной коробки.Я ценю, что есть потеря точности из-за того, что невозможно представить 0,33 повторения, когда вы имеете дело с конечным числом битов.

Вопрос: Для людей, которые имеют больше опыта в системах, которым необходимо использовать округление (возможно, финансовое), какие у меня есть варианты решения этой проблемы?Как бы вы сделали, чтобы удаление последнего могло означать, что ящик больше не существует?

Редактировать:
В конце концов.Я использовал предложение Лорен Печтел и сохранил количество банок, затем, когда мне нужно показать, сколько стандартных ящиков я делю общее количество банок на количество банок в стандартном ящике, которое все еще дает рекурсивный результат, но это хорошо для отчетностисторона вещей.

Вот код, который, я надеюсь, поможет обрисовать проблему еще немного: -

     static void Main(string[] args)
     {
        var box = new StandardBox(3);
        var boxDetail = new BoxDetail(1.0m, box);

        var allBoxes = new AllBoxes();
        allBoxes.AddBox(boxDetail);
        allBoxes.RemoveItemFromBox(boxDetail, 1.0m);
        Console.WriteLine(allBoxes);
        allBoxes.RemoveItemFromBox(boxDetail, 1.0m);
        Console.WriteLine(allBoxes);
        allBoxes.RemoveItemFromBox(boxDetail, 1.0m);
        Console.WriteLine(allBoxes);
        Console.ReadLine();
    }
}

public class StandardBox
{
    private decimal _quantity;
    public StandardBox(decimal quantity){_quantity = quantity;}
    public decimal Quantity {get { return _quantity; }}
}

public class BoxDetail
{
    private decimal _quantity;
    private StandardBox _box;

    public BoxDetail(decimal quantity, StandardBox box)
    {
        _quantity = quantity;
        _box = box;
    }

    public void RemoveItem(decimal quantity)
    {
        var newQuantity = quantity / _box.Quantity;
        _quantity = _quantity - newQuantity;
    }

    public override string ToString()
    {
        return _quantity.ToString() + " of " + _box.Quantity.ToString();
    }
}

public class AllBoxes
{
    private List<BoxDetail> allBoxes = new List<BoxDetail>();

    public AllBoxes(){}

    public void AddBox(BoxDetail box){allBoxes.Add(box);}

    public void RemoveItemFromBox(BoxDetail box, decimal quantity)
    {
        var boxDetailLineToModify = allBoxes.Find(b => b.Equals(box));
        boxDetailLineToModify.RemoveItem(quantity);
    }

    public override string ToString()
    {
        var results = string.Empty;
        foreach (var box in allBoxes)
        {
            results += box.ToString() + "\n";
        }
        return results;
    } 
}

Ответы [ 3 ]

7 голосов
/ 03 марта 2012

Мой подход к таким проблемам - не делай этого.

Это особенно верно, когда речь идет о финансовых вещах.

С финансовыми вопросами, как правило, довольно легко разобраться - делай всеработа в копейках, а не в долларах.

Моя первая реакция на вашу проблему - хранить банки, а не коробки с банками.

2 голосов
/ 03 марта 2012

Почему бы не использовать int для ваших количеств?Не похоже, что вы все равно хотите разрешить удаление банок 1.078261563 (например) - не так ли?

Прочитав немного больше, я думаю, что вижу здесь дилемму.Похоже, что вы объединяете размер (максимальная вместимость коробки), количество (то, что в данный момент содержится в коробке) в просто процент полного (отношение количества к размеру).Если вы немного измените дизайн, отслеживая размер и количество и вычисляя Процент заполнения, я думаю, что все прояснится:

class Box {
    public decimal Size { get; private set; }
    public decimal Quantity { get; private set; }
    public decimal PercentFull { get { return this.Quantity / this.Size; } }
    public Box(decimal size) {
        this.Size = size;
    }
    public void Remove(decimal quantity) 
    {
        this.Quantity -= quantity;
    }
    public void Add(decimal quantity) 
    {
        this.Quantity += quantity;
    }
    public override string ToString() {
        return this.PercentFull.ToString() + "% of " + this.Size.ToString();
            // Or, to be exact
            // return this.Quantity.ToString() + " of " + this.Size.ToString();
    }
}

Это все еще кажется, основываясь на вашем (возможно, очищенном) примеречто Size и Quantity лучше подходят для int - но математика работает в любом случае.

1 голос
/ 03 марта 2012

Если вы просто хотите отобразить 0, то установите точность при отображении чего-либо разумного, скажем, 2 десятичных знака:

public override string ToString()
{
    return _quantity.ToString("D2") + " of " + _box.Quantity.ToString();
}

Если вы хотите сравнить значение с 0 (или любым другим значением), сравните разницу с некоторым небольшим значением:

public bool AreEqual(double value1, double value2)
{
    double EPSILON = 0.000001;
    return Math.Abs(value1 - value2) < EPSILON;
}

bool IsZero = AreEqual(_quantity,0);
...