Доказательство правильности алгоритма распределения денег Фаулера

Мартин Фаулер имеет класс денег это имеет процедуру распределения денег. Эта процедура распределяет деньги в соответствии с заданным списком коэффициентов без потери какой-либо ценности путем округления. Он распределяет любое остаточное значение по результатам.

например, $100, выделенные "коэффициентами" (1, 1, 1), дадут ($34, $33, $33).

здесь allocate функция:

public long[] allocate(long amount, long[] ratios) {
    long total = 0;
    for (int i = 0; i < ratios.length; i++) total += ratios[i];

    long remainder = amount;
    long[] results = new long[ratios.length];
    for (int i = 0; i < results.length; i++) {
        results[i] = amount * ratios[i] / total;
        remainder -= results[i];
    }

    for (int i = 0; i < remainder; i++) {
        results[i]++;
    }

    return results;
}

(ради этого вопроса, чтобы сделать его проще, я взял на свобода замены денежных типов на длинные.)

вопрос, откуда я знаю, что это правильно? Все это кажется самоочевидным, за исключением финала цикла. Я думаю, что для доказательства правильности функции было бы достаточно доказать, что в конечном for-цикле верно следующее соотношение:

remainder < results.length

кто-нибудь может это доказать?

4 ответов


ключевое понимание заключается в том, что общий остаток равен сумме отдельных остатков при расчете каждого result[i].

Так как каждый отдельный остаток является результатом округления, это не более 1. Есть results.length такие остатки, поэтому общий остаток не более results.length.

EDIT: очевидно, что это не доказательство без некоторых красивых символов, так что вот некоторые...
alt text


никаких доказательств не требуется.

базовые суммы распределяются простым делением, округляя вниз. Так что выделенная сумма всегда будет меньше или равна общей.

остаток содержит нераспределенную сумму. Которое всегда будет целым числом меньше, чем "я". Поэтому он просто дает каждому получателю 1, пока деньги не уйдут.


простой

просто использовать то, что

a=этаж(a/b)*b+(a%b)


Я бы сказал, что это неправильно, потому что какое-то любопытное соотношение может вызвать остаток больше, чем количество результатов. Поэтому я предлагаю results[i % results.length].amount++;.

Edit: я снимаю свой ответ. С лонгами нет любопытного соотношения, а с плавающей запятой по модулю не помогает