Question

Nous avons un coût C qui doit être attribué aux départements 1..n. Un autre calcul produit la part de chaque département, il s’agit d’un nombre compris entre 0 et 1, avec plus de 5 décimales. La somme de toutes les parts de département est exactement 1, mais elles ne sont pas nécessairement égales.

L'objectif est de calculer les dollars et les cents exacts à facturer à chaque département. La somme de la facture doit EXACTEMENT correspondre au coût C, il ne peut pas y avoir quelques centimes de moins ou plus. En outre, la part de chaque département ne doit pas nécessiter de fractions de centimes. De plus, bien qu'il ne soit pas juste de simplement déposer le reste sur le dernier département, il n'est pas nécessaire de regarder en arrière dans les délais précédents. Notez qu'en arrondissant la part de chaque département au centime, il en résulte presque toujours un excédent / sous-pondération de quelques sous.

Exemple très simpliste: C = 33,34, 4 départements avec chacun exactement 0.2500 actions. Parce que 33,34 * 0,25 = 8,335, vous pouvez donc voir que deux départements doivent payer 8,33 et deux doivent payer 8,34. Une affectation correcte serait: d1 = 8,33, d2 = 8,34, d3 = 8,33, d4 = 8,34. Si vous arrondissez, chaque département paie 8,34, ce qui entraîne un dépassement de 0,02 dollar. Si vous multipliez cela par beaucoup plus de ministères et beaucoup plus de coûts, vous vous retrouverez avec des écarts de cent dollars.

Je veux le faire en un seul passage, c’est-à-dire que je ne veux pas passer en boucle, découvrir que je suis désactivé de 0,02, puis relancer et modifier les valeurs jusqu’à ce qu’il soit correct. Je veux faire cela en 1 passe. J'aimerais aussi savoir si cet algorithme a un nom.

Était-ce utile?

La solution

En 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;
        

En Perl:

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

Sortie:

<*> /= $denom for @shares; } return \@alloc; }

Sortie:

<*>

Autres conseils

N'utilisez pas de flotteurs. Utilisez soit des nombres décimaux à virgule fixe, soit des valeurs telles que vous pouvez diviser par n'importe quel facteur d'échelle pour obtenir la valeur en dollars.

Jamais, jamais, jamais ne calculez votre argent à l'aide de flotteurs.

Je l'appellerais "total cumulé". et le mettre en œuvre comme suit:

  1. Calculez la part due par le département n ° 1 (peut contenir des erreurs d'arrondi)
  2. Assurez que le département 1 verse le montant calculé à l’étape 1
  3. Calculez la part qui revient conjointement aux départements 1 et 2
  4. Faites en sorte que le département 1 verse le montant calculé à l’étape 3, moins le montant réellement versé à l’étape 2.
  5. Etc. pour le montant dû conjointement par les ministères nos 1, 2 et 3.

Cela garantira que l'erreur rencontrée par un service quelconque est au plus une erreur d'arrondi (et non la somme de plusieurs erreurs d'arrondi), et que le total final est correct.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top