точное вычисление пенни за один проход - PullRequest
1 голос
/ 01 декабря 2009

У нас есть стоимость C, которая должна быть отнесена к отделам 1..n. Другое вычисление производит долю для каждого отдела, это число от 0 до 1, с более чем 5 десятичными разрядами. Сумма всех долей департамента равна 1, но они не обязательно равны.

Цель состоит в том, чтобы вычислить точные доллары и центы для выставления счетов каждому отделу. Сумма счета должна ТОЧНО соответствовать стоимости C, она не может быть меньше или меньше нескольких копеек. Кроме того, доля каждого департамента не должна требовать дробных копеек. Кроме того, хотя несправедливо просто сбрасывать остаток в последний отдел, нет необходимости возвращаться к предыдущим временным рамкам. Обратите внимание, что простое округление доли каждого департамента до копейки почти всегда приводит к превышению / уменьшению нескольких пенни.

Пример грубого упрощения: C = 33,34, 4 отдела, каждый с долей 0,2500. Потому что 33,34 * 0,25 = 8,335, поэтому вы можете видеть, что два департамента должны платить 8,33, а два должны платить 8,34. Одно правильное назначение: d1 = 8,33, d2 = 8,34, d3 = 8,33, d4 = 8,34. Если вы округлите, каждый отдел платит 8,34, что приводит к переизбытку 0,02 доллара. Если вы умножите это на гораздо большее количество отделов и еще больше затрат, вы получите 100 долларовых расхождений.

Я хочу сделать это за 1 проход, то есть я не хочу циклически проходить, обнаруживать, что я выключен на 0,02, затем снова зацикливаться и настраивать значения до тех пор, пока он не будет правильным. Я хочу сделать это за 1 проход. Я также хотел бы знать, есть ли у этого алгоритма имя.

Ответы [ 3 ]

2 голосов
/ 01 декабря 2009

Не используйте поплавки. Используйте десятичные числа с фиксированной запятой или целые числа, чтобы вы могли делить на любой коэффициент масштабирования, чтобы получить значение в долларах.

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

1 голос
/ 01 декабря 2009

Я бы назвал это «промежуточным итогом» и реализовал бы его следующим образом:

  1. Рассчитать долю, причитающуюся с отдела № 1 (может иметь ошибки округления)
  2. Заставить отдел № 1 оплатить сумму, рассчитанную на шаге 1
  3. Рассчитайте долю в долгах между отделами № 1 и № 2
  4. Заставить отдел № 1 платить сумму, рассчитанную на шаге 3, за вычетом суммы, фактически уплаченной на шаге 2.
  5. Etc. на сумму, которая в долгу перед депозитами #s 1, 2 и 3.

Это обеспечит, чтобы ошибка, с которой сталкивался любой отдел, составляла не более одной ошибки округления (а не суммы нескольких ошибок округления), а итоговая сумма была правильной.

1 голос
/ 01 декабря 2009

В Perl:

#!/usr/bin/perl

use strict;
use warnings;

use Data::Dumper;

use List::AllUtils qw( sum );

print Dumper allocate(10_000, .23, .37, .4);
print Dumper allocate(33.34, .25, .25, .25, .25);
print Dumper allocate(100, 1/3, 1/3, 1/3);

sub allocate {
    my ($C, @shares) = @_;

    my @alloc;

    while ( my $share = shift @shares ) {
        push @alloc, sprintf '%.2f', $C * $share;
        $C -= $alloc[-1];
        my $denom = sum @shares;
        $_ /= $denom for @shares;
    }

    return \@alloc;
}

Вывод:

$VAR1 = [
          '2300.00',
          '3700.00',
          '4000.00'
        ];
$VAR1 = [
          '8.34',
          '8.33',
          '8.34',
          '8.33'
        ];
$VAR1 = [
          '33.33',
          '33.34',
          '33.33'
        ];
...