web-dev-qa-db-fra.com

Comment faire pour que les pourcentages arrondis totalisent 100%

Considérons les quatre pourcentages ci-dessous, représentés par des nombres float:

    13.626332%
    47.989636%
     9.596008%
    28.788024%
   -----------
   100.000000%

Je dois représenter ces pourcentages sous forme de nombres entiers. Si j’utilise simplement Math.round(), j’ai un total de 101%. 

14 + 48 + 10 + 29 = 101

Si j'utilise parseInt(), je termine avec un total de 97%. 

13 + 47 + 9 + 28 = 97

Qu'est-ce qu'un bon algorithme pour représenter un nombre quelconque de pourcentages tout en maintenant un total de 100%?


Edit : Après avoir lu certains commentaires et réponses, il existe clairement de nombreux moyens de résoudre ce problème.

Dans mon esprit, pour rester fidèle aux chiffres, le résultat "correct" est celui qui minimise l'erreur globale, défini par le degré d'erreur que l'arrondi introduirait par rapport à la valeur réelle:

        value  rounded     error               decision
   ----------------------------------------------------
    13.626332       14      2.7%          round up (14)
    47.989636       48      0.0%          round up (48)
     9.596008       10      4.0%    don't round up  (9)
    28.788024       29      2.7%          round up (29)

En cas d’égalité (3,33, 3,33, 3,33), une décision arbitraire peut être prise (par exemple 3, 4, 3).

155
poezn

Comme aucune des réponses ici ne semble résoudre le problème correctement, voici ma version semi-obscurcie utilisant underscorejs :

function foo(l, target) {
    var off = target - _.reduce(l, function(acc, x) { return acc + Math.round(x) }, 0);
    return _.chain(l).
            sortBy(function(x) { return Math.round(x) - x }).
            map(function(x, i) { return Math.round(x) + (off > i) - (i >= (l.length + off)) }).
            value();
}

foo([13.626332, 47.989636, 9.596008, 28.788024], 100) // => [48, 29, 14, 9]
foo([16.666, 16.666, 16.666, 16.666, 16.666, 16.666], 100) // => [17, 17, 17, 17, 16, 16]
foo([33.333, 33.333, 33.333], 100) // => [34, 33, 33]
foo([33.3, 33.3, 33.3, 0.1], 100) // => [34, 33, 33, 0]
30
yonilevy

Il existe de nombreuses façons de le faire, à condition que vous ne craigniez pas de vous fier aux données décimales d'origine.

La première méthode, et peut-être la plus populaire, serait la Méthode du plus grand reste

Ce qui est fondamentalement:

  1. Tout arrondir
  2. Obtenir la différence en somme et 100
  3. Distribuer la différence en ajoutant 1 aux éléments dans l'ordre décroissant de leurs parties décimales

Dans votre cas, cela ressemblerait à ceci:

13.626332%
47.989636%
 9.596008%
28.788024%

Si vous prenez les parties entières, vous obtenez

13
47
 9
28

ce qui donne 97, et vous voulez en ajouter trois autres. Maintenant, vous regardez les parties décimales, qui sont

.626332%
.989636%
.596008%
.788024%

et prenez les plus gros jusqu'à ce que le total atteigne 100. Vous obtiendriez donc:

14
48
 9
29

Sinon, vous pouvez simplement choisir d'afficher une décimale au lieu des valeurs entières. Ainsi, les nombres seraient 48,3 et 23,9, etc. Cela réduirait considérablement la variance de 100.

138
Varun Vohra

La meilleure façon de le faire est probablement de garder un compte courant (non intégral) de votre position et de arrondir la valeur , puis de l'utiliser avec l'historique pour déterminer quelle valeur doit être utilisée. Par exemple, en utilisant les valeurs que vous avez données:

Value      CumulValue  CumulRounded  PrevBaseline  Need
---------  ----------  ------------  ------------  ----
                                  0
13.626332   13.626332            14             0    14 ( 14 -  0)
47.989636   61.615968            62            14    48 ( 62 - 14)
 9.596008   71.211976            71            62     9 ( 71 - 62)
28.788024  100.000000           100            71    29 (100 - 71)
                                                    ---
                                                    100

A chaque étape, vous n’arrondez pas le nombre lui-même. Au lieu de cela, arrondissez la valeur accumulated et calculez le meilleur nombre entier qui atteint cette valeur depuis la référence précédente - cette référence est la valeur cumulative (arrondie) de la ligne précédente.

Cela fonctionne parce que vous ne perdez pas d'informations à chaque étape, mais que vous les utilisez de manière plus intelligente. Les valeurs arrondies "correctes" sont dans la dernière colonne et vous pouvez voir qu'elles totalisent 100.

21
paxdiablo

Le but de l'arrondi est de générer le moins d'erreur possible. Lorsque vous arrondissez une valeur unique, ce processus est simple et direct et la plupart des gens le comprennent facilement. Lorsque vous arrondissez plusieurs nombres en même temps, le processus devient plus compliqué - vous devez définir comment les erreurs vont se combiner, c’est-à-dire ce qui doit être minimisé.

Le réponse bien votée de Varun Vohra minimise la somme des erreurs absolues et est très simple à mettre en œuvre. Toutefois, il n’ya pas de cas Edge qu’il ne gère pas - quel devrait être le résultat de l’arrondi de 24.25, 23.25, 27.25, 25.25? Un de ceux-ci doit être arrondi au lieu de bas. Vous choisiriez probablement de manière arbitraire le premier ou le dernier de la liste.

Il est peut-être préférable d’utiliser l’erreur relative plutôt que l’erreur absolute. Arrondir 23,25 jusqu'à 24 le change de 3,2%, tandis que arrondir 27,25 à 28 le change seulement de 2,8%. Maintenant, il y a un gagnant clair.

Il est possible de modifier cela encore plus. Une technique courante consiste à square chaque erreur, de sorte que les grandes erreurs comptent de manière disproportionnée plus que les petites. Je voudrais également utiliser un diviseur non linéaire pour obtenir l'erreur relative - il ne semble pas exact qu'une erreur à 1% soit 99 fois plus importante qu'une erreur à 99%. Dans le code ci-dessous, j'ai utilisé la racine carrée.

L'algorithme complet est le suivant:

  1. Additionnez les pourcentages après les avoir arrondis tous en bas et soustrayez-vous à 100. Cela vous indique combien de ces pourcentages doivent être arrondis à la place.
  2. Générez deux scores d'erreur pour chaque pourcentage, un lorsque arrondi et un arrondi. Prenez la différence entre les deux.
  3. Triez les différences d'erreur produites ci-dessus.
  4. Pour le nombre de pourcentages à arrondir, prenez un élément de la liste triée et incrémentez le pourcentage arrondi de 1.

Vous pouvez toujours avoir plus d'une combinaison avec la même somme d'erreur, par exemple 33.3333333, 33.3333333, 33.3333333. Ceci est inévitable et le résultat sera complètement arbitraire. Le code que je donne ci-dessous préfère arrondir les valeurs à gauche.

Tout mettre en place en Python ressemble à ceci.

def error_gen(actual, rounded):
    divisor = sqrt(1.0 if actual < 1.0 else actual)
    return abs(rounded - actual) ** 2 / divisor

def round_to_100(percents):
    if not isclose(sum(percents), 100):
        raise ValueError
    n = len(percents)
    rounded = [int(x) for x in percents]
    up_count = 100 - sum(rounded)
    errors = [(error_gen(percents[i], rounded[i] + 1) - error_gen(percents[i], rounded[i]), i) for i in range(n)]
    rank = sorted(errors)
    for i in range(up_count):
        rounded[rank[i][1]] += 1
    return rounded

>>> round_to_100([13.626332, 47.989636, 9.596008, 28.788024])
[14, 48, 9, 29]
>>> round_to_100([33.3333333, 33.3333333, 33.3333333])
[34, 33, 33]
>>> round_to_100([24.25, 23.25, 27.25, 25.25])
[24, 23, 28, 25]
>>> round_to_100([1.25, 2.25, 3.25, 4.25, 89.0])
[1, 2, 3, 4, 90]

Comme vous pouvez le constater avec ce dernier exemple, cet algorithme est toujours capable de fournir des résultats non intuitifs. Bien que 89.0 ne nécessite aucun arrondi, l’une des valeurs de cette liste doit être arrondie; l'erreur relative la plus faible résulte de l'arrondissement de cette valeur élevée plutôt que des solutions beaucoup plus petites.

Cette réponse préconisait à l’origine de passer par toutes les combinaisons possibles d’arrondis/décroissants, mais comme le soulignent les commentaires, une méthode plus simple fonctionne mieux. L'algorithme et le code reflètent cette simplification.

12
Mark Ransom

NE PAS additionner les nombres arrondis. Vous allez avoir des résultats inexacts. Le total pourrait être considérablement réduit en fonction du nombre de termes et de la distribution des fractions. 

Affiche les nombres arrondis mais somme les valeurs réelles. Selon la manière dont vous présentez les chiffres, la manière de procéder varie. De cette façon, vous obtenez

  14 
 48 
 dix
 29 
 __
 100

De toute façon, vous allez avoir des divergences. Dans votre exemple, il n’ya aucun moyen d’indiquer des nombres qui totalisent 100 sans "arrondir" une valeur dans le mauvais sens (la moindre erreur serait de changer 9,596 en 9)

MODIFIER

Vous devez choisir entre l’un des éléments suivants:

  1. Précision des objets
  2. Précision de la somme (si vous additionnez des valeurs arrondies)
  3. Cohérence entre les éléments arrondis et la somme arrondie)

La plupart du temps, s’agissant du pourcentage n ° 3, c’est la meilleure option car elle est plus évidente lorsque le total est égal à 101% que lorsque les éléments individuels ne totalisent pas 100, et que vous conservez la précision des éléments. "Arrondir" 9.596 à 9 est inexact à mon avis.

Pour expliquer cela, j'ajoute parfois une note de bas de page expliquant que les valeurs individuelles sont arrondies et ne totalisent peut-être pas 100%. Toute personne qui comprend l'arrondi devrait être en mesure de comprendre cette explication.

7
D Stanley

J'ai écrit un assistant d'arrondi à la version C #, l'algorithme est le même que La réponse de Varun Vohra , espérons que cela aide.

public static List<decimal> GetPerfectRounding(List<decimal> original,
    decimal forceSum, int decimals)
{
    var rounded = original.Select(x => Math.Round(x, decimals)).ToList();
    Debug.Assert(Math.Round(forceSum, decimals) == forceSum);
    var delta = forceSum - rounded.Sum();
    if (delta == 0) return rounded;
    var deltaUnit = Convert.ToDecimal(Math.Pow(0.1, decimals)) * Math.Sign(delta);

    List<int> applyDeltaSequence; 
    if (delta < 0)
    {
        applyDeltaSequence = original
            .Zip(Enumerable.Range(0, int.MaxValue), (x, index) => new { x, index })
            .OrderBy(a => original[a.index] - rounded[a.index])
            .ThenByDescending(a => a.index)
            .Select(a => a.index).ToList();
    }
    else
    {
        applyDeltaSequence = original
            .Zip(Enumerable.Range(0, int.MaxValue), (x, index) => new { x, index })
            .OrderByDescending(a => original[a.index] - rounded[a.index])
            .Select(a => a.index).ToList();
    }

    Enumerable.Repeat(applyDeltaSequence, int.MaxValue)
        .SelectMany(x => x)
        .Take(Convert.ToInt32(delta/deltaUnit))
        .ForEach(index => rounded[index] += deltaUnit);

    return rounded;
}

Il réussit le test unitaire suivant:

[TestMethod]
public void TestPerfectRounding()
{
    CollectionAssert.AreEqual(Utils.GetPerfectRounding(
        new List<decimal> {3.333m, 3.334m, 3.333m}, 10, 2),
        new List<decimal> {3.33m, 3.34m, 3.33m});

    CollectionAssert.AreEqual(Utils.GetPerfectRounding(
        new List<decimal> {3.33m, 3.34m, 3.33m}, 10, 1),
        new List<decimal> {3.3m, 3.4m, 3.3m});

    CollectionAssert.AreEqual(Utils.GetPerfectRounding(
        new List<decimal> {3.333m, 3.334m, 3.333m}, 10, 1),
        new List<decimal> {3.3m, 3.4m, 3.3m});


    CollectionAssert.AreEqual(Utils.GetPerfectRounding(
        new List<decimal> { 13.626332m, 47.989636m, 9.596008m, 28.788024m }, 100, 0),
        new List<decimal> {14, 48, 9, 29});
    CollectionAssert.AreEqual(Utils.GetPerfectRounding(
        new List<decimal> { 16.666m, 16.666m, 16.666m, 16.666m, 16.666m, 16.666m }, 100, 0),
        new List<decimal> { 17, 17, 17, 17, 16, 16 });
    CollectionAssert.AreEqual(Utils.GetPerfectRounding(
        new List<decimal> { 33.333m, 33.333m, 33.333m }, 100, 0),
        new List<decimal> { 34, 33, 33 });
    CollectionAssert.AreEqual(Utils.GetPerfectRounding(
        new List<decimal> { 33.3m, 33.3m, 33.3m, 0.1m }, 100, 0),
        new List<decimal> { 34, 33, 33, 0 });
}
6
Bruce

Vous pouvez essayer de garder une trace de votre erreur en raison de l'arrondissement, puis de l'arrondir dans le sens contraire du grain si l'erreur accumulée est supérieure à la fraction du nombre en cours.

13.62 -> 14 (+.38)
47.98 -> 48 (+.02 (+.40 total))
 9.59 -> 10 (+.41 (+.81 total))
28.78 -> 28 (round down because .81 > .78)
------------
        100

Je ne sais pas si cela fonctionnerait en général, mais cela semble fonctionner de manière similaire si l'ordre est inversé:

28.78 -> 29 (+.22)
 9.59 ->  9 (-.37; rounded down because .59 > .22)
47.98 -> 48 (-.35)
13.62 -> 14 (+.03)
------------
        100

Je suis sûr qu'il y a des cas où Edge pourrait s'effondrer, mais toute approche sera au moins quelque peu arbitraire puisque vous modifiez essentiellement vos données d'entrée.

4
atkretsch

J'ai déjà écrit un outil non arrondi pour trouver la perturbation minimale d'un ensemble de nombres correspondant à un objectif. C'était un problème différent, mais on pourrait en théorie utiliser une idée similaire ici. Dans ce cas, nous avons un ensemble de choix.

Ainsi, pour le premier élément, nous pouvons arrondir le nombre à 14 ou le réduire à 13. Le coût (au sens binaire de la programmation entière) est moins pour le arrondi que pour le arrondi, car il faut arrondir déplacez cette valeur sur une plus grande distance. De la même manière, nous pouvons arrondir chaque nombre vers le haut ou le bas, de sorte que nous devons choisir parmi 16 choix.

  13.626332
  47.989636
   9.596008
+ 28.788024
-----------
 100.000000

Normalement, je résoudrais le problème général dans MATLAB, ici en utilisant bintprog, un outil de programmation entier binaire, mais il n’ya que quelques choix à tester. Il est donc assez facile, avec de simples boucles, de tester chacune des 16 alternatives. Par exemple, supposons que nous arrondissions cet ensemble comme suit:

 Original      Rounded   Absolute error
   13.626           13          0.62633
    47.99           48          0.01036
    9.596           10          0.40399
 + 28.788           29          0.21198
---------------------------------------
  100.000          100          1.25266

L'erreur absolue totale est 1.25266. Il peut être légèrement réduit par l’arrondi alternatif suivant:

 Original      Rounded   Absolute error
   13.626           14          0.37367
    47.99           48          0.01036
    9.596            9          0.59601
 + 28.788           29          0.21198
---------------------------------------
  100.000          100          1.19202

En fait, ce sera la solution optimale en termes d'erreur absolue. Bien sûr, s’il y avait 20 termes, l’espace de recherche sera de taille 2 ^ 20 = 1048576. Pour 30 ou 40 termes, cet espace aura une taille importante. Dans ce cas, vous devrez utiliser un outil capable de rechercher efficacement dans l'espace, en utilisant éventuellement un schéma de branche et lié.

2
user85109

Je pense que ce qui suit réalisera ce que vous recherchez

function func( orig, target ) {

    var i = orig.length, j = 0, total = 0, change, newVals = [], next, factor1, factor2, len = orig.length, marginOfErrors = [];

    // map original values to new array
    while( i-- ) {
        total += newVals[i] = Math.round( orig[i] );
    }

    change = total < target ? 1 : -1;

    while( total !== target ) {

        // Iterate through values and select the one that once changed will introduce
        // the least margin of error in terms of itself. e.g. Incrementing 10 by 1
        // would mean an error of 10% in relation to the value itself.
        for( i = 0; i < len; i++ ) {

            next = i === len - 1 ? 0 : i + 1;

            factor2 = errorFactor( orig[next], newVals[next] + change );
            factor1 = errorFactor( orig[i], newVals[i] + change );

            if(  factor1 > factor2 ) {
                j = next; 
            }
        }

        newVals[j] += change;
        total += change;
    }


    for( i = 0; i < len; i++ ) { marginOfErrors[i] = newVals[i] && Math.abs( orig[i] - newVals[i] ) / orig[i]; }

    // Math.round() causes some problems as it is difficult to know at the beginning
    // whether numbers should have been rounded up or down to reduce total margin of error. 
    // This section of code increments and decrements values by 1 to find the number
    // combination with least margin of error.
    for( i = 0; i < len; i++ ) {
        for( j = 0; j < len; j++ ) {
            if( j === i ) continue;

            var roundUpFactor = errorFactor( orig[i], newVals[i] + 1)  + errorFactor( orig[j], newVals[j] - 1 );
            var roundDownFactor = errorFactor( orig[i], newVals[i] - 1) + errorFactor( orig[j], newVals[j] + 1 );
            var sumMargin = marginOfErrors[i] + marginOfErrors[j];

            if( roundUpFactor < sumMargin) { 
                newVals[i] = newVals[i] + 1;
                newVals[j] = newVals[j] - 1;
                marginOfErrors[i] = newVals[i] && Math.abs( orig[i] - newVals[i] ) / orig[i];
                marginOfErrors[j] = newVals[j] && Math.abs( orig[j] - newVals[j] ) / orig[j];
            }

            if( roundDownFactor < sumMargin ) { 
                newVals[i] = newVals[i] - 1;
                newVals[j] = newVals[j] + 1;
                marginOfErrors[i] = newVals[i] && Math.abs( orig[i] - newVals[i] ) / orig[i];
                marginOfErrors[j] = newVals[j] && Math.abs( orig[j] - newVals[j] ) / orig[j];
            }

        }
    }

    function errorFactor( oldNum, newNum ) {
        return Math.abs( oldNum - newNum ) / oldNum;
    }

    return newVals;
}


func([16.666, 16.666, 16.666, 16.666, 16.666, 16.666], 100); // => [16, 16, 17, 17, 17, 17]
func([33.333, 33.333, 33.333], 100); // => [34, 33, 33]
func([33.3, 33.3, 33.3, 0.1], 100); // => [34, 33, 33, 0] 
func([13.25, 47.25, 11.25, 28.25], 100 ); // => [13, 48, 11, 28]
func( [25.5, 25.5, 25.5, 23.5], 100 ); // => [25, 25, 26, 24]

Une dernière chose, j'ai lancé la fonction en utilisant les nombres donnés à l'origine dans la question pour les comparer

func([13.626332, 47.989636, 9.596008, 28.788024], 100); // => [48, 29, 13, 10]

C'était différent de ce que la question voulait => [48, 29, 14, 9]. Je ne pouvais pas comprendre cela avant de regarder la marge d'erreur totale

-------------------------------------------------
| original  | question | % diff | mine | % diff |
-------------------------------------------------
| 13.626332 | 14       | 2.74%  | 13   | 4.5%   |
| 47.989636 | 48       | 0.02%  | 48   | 0.02%  |
| 9.596008  | 9        | 6.2%   | 10   | 4.2%   |
| 28.788024 | 29       | 0.7%   | 29   | 0.7%   |
-------------------------------------------------
| Totals    | 100      | 9.66%  | 100  | 9.43%  |
-------------------------------------------------

Essentiellement, le résultat de ma fonction introduit le moins d’erreurs possible.

Violon ici

2
Bruno

Je ne suis pas sûr du niveau de précision dont vous avez besoin, mais ce que je ferais, c’est tout simplement d’ajouter 1 le premier nombre n, n étant le plafond de la somme totale des décimales. Dans ce cas, il s'agit de 3, donc j'ajouterais 1 aux 3 premiers éléments et répartirais le reste. Bien sûr, ce n’est pas très précis. Certains chiffres peuvent être arrondis à la hausse ou à la baisse, mais cela fonctionne bien et donnera toujours un résultat de 100%.

Donc, [ 13.626332, 47.989636, 9.596008, 28.788024 ] serait [14, 48, 10, 28] car Math.ceil(.626332+.989636+.596008+.788024) == 3

function evenRound( arr ) {
  var decimal = -~arr.map(function( a ){ return a % 1 })
    .reduce(function( a,b ){ return a + b }); // Ceil of total sum of decimals
  for ( var i = 0; i < decimal; ++i ) {
    arr[ i ] = ++arr[ i ]; // compensate error by adding 1 the the first n items
  }
  return arr.map(function( a ){ return ~~a }); // floor all other numbers
}

var nums = evenRound( [ 13.626332, 47.989636, 9.596008, 28.788024 ] );
var total = nums.reduce(function( a,b ){ return a + b }); //=> 100

Vous pouvez toujours informer les utilisateurs que les chiffres sont arrondis et peuvent ne pas être super précis ...

1
elclanrs

Si vous devez vraiment les arrondir, il y a déjà de très bonnes suggestions ici (plus gros reste, moins erreur relative, etc.).

Il y a aussi déjà une bonne raison de ne pas arrondir (vous aurez au moins un chiffre qui "aura l'air mieux" mais qui est "faux"), et comment résoudre ce problème (avertissez vos lecteurs) et c'est ce que je fais.

Permettez-moi d'ajouter le "mauvais" numéro.

Supposons que vous avez trois événements/entités/... avec quelques pourcentages que vous estimez comme:

DAY 1
who |  real | app
----|-------|------
  A | 33.34 |  34
  B | 33.33 |  33
  C | 33.33 |  33

Plus tard, les valeurs changent légèrement, à

DAY 2
who |  real | app
----|-------|------
  A | 33.35 |  33
  B | 33.36 |  34
  C | 33.29 |  33

La première table a le problème déjà mentionné d'avoir un "mauvais" numéro: 33.34 est plus proche de 33 que de 34.

Mais maintenant, vous avez une plus grande erreur. En comparant les jours 2 et 1, le pourcentage réel de A a augmenté de 0,01%, mais l’approximation montre une diminution de 1%.

C'est une erreur qualitative, probablement bien pire que l'erreur quantitative initiale.

On pourrait imaginer une approximation pour l’ensemble, mais il se peut que vous deviez publier des données le premier jour. Par conséquent, vous ne saurez rien du deuxième jour. Donc, à moins que vous ne deviez vraiment, vraiment, approximer, vous feriez probablement mieux de ne pas.

1
Rolazaro Azeveires

Si vous l'arrondissez, il n'y a pas de bonne façon de l'obtenir de la même manière dans tous les cas. 

Vous pouvez prendre la partie décimale des N pourcentages que vous avez (dans l’exemple que vous avez donné, c’est 4). 

Ajoutez les parties décimales. Dans votre exemple, vous avez le total de la partie fractionnaire = 3.

Ceil les 3 nombres avec les fractions les plus élevées et sol le reste.

(Désolé pour les modifications)

1
arunlalam

Voici une implémentation Python plus simple de la réponse @ varun-vohra:

def apportion_pcts(pcts, total):
    proportions = [total * (pct / 100) for pct in pcts]
    apportions = [math.floor(p) for p in proportions]
    remainder = total - sum(apportions)
    remainders = [(i, p - math.floor(p)) for (i, p) in enumerate(proportions)]
    remainders.sort(key=operator.itemgetter(1), reverse=True)
    for (i, _) in itertools.cycle(remainders):
        if remainder == 0:
            break
        else:
            apportions[i] += 1
            remainder -= 1
    return apportions

Vous avez besoin de math, itertools, operator.

0
CMCDragonkai

J'ai mis en œuvre la méthode de la réponse de Varun Vohra ici pour les listes et les dicts.

import math
import numbers
import operator
import itertools


def round_list_percentages(number_list):
    """
    Takes a list where all values are numbers that add up to 100,
    and rounds them off to integers while still retaining a sum of 100.

    A total value sum that rounds to 100.00 with two decimals is acceptable.
    This ensures that all input where the values are calculated with [fraction]/[total]
    and the sum of all fractions equal the total, should pass.
    """
    # Check input
    if not all(isinstance(i, numbers.Number) for i in number_list):
        raise ValueError('All values of the list must be a number')

    # Generate a key for each value
    key_generator = itertools.count()
    value_dict = {next(key_generator): value for value in number_list}
    return round_dictionary_percentages(value_dict).values()


def round_dictionary_percentages(dictionary):
    """
    Takes a dictionary where all values are numbers that add up to 100,
    and rounds them off to integers while still retaining a sum of 100.

    A total value sum that rounds to 100.00 with two decimals is acceptable.
    This ensures that all input where the values are calculated with [fraction]/[total]
    and the sum of all fractions equal the total, should pass.
    """
    # Check input
    # Only allow numbers
    if not all(isinstance(i, numbers.Number) for i in dictionary.values()):
        raise ValueError('All values of the dictionary must be a number')
    # Make sure the sum is close enough to 100
    # Round value_sum to 2 decimals to avoid floating point representation errors
    value_sum = round(sum(dictionary.values()), 2)
    if not value_sum == 100:
        raise ValueError('The sum of the values must be 100')

    # Initial floored results
    # Does not add up to 100, so we need to add something
    result = {key: int(math.floor(value)) for key, value in dictionary.items()}

    # Remainders for each key
    result_remainders = {key: value % 1 for key, value in dictionary.items()}
    # Keys sorted by remainder (biggest first)
    sorted_keys = [key for key, value in sorted(result_remainders.items(), key=operator.itemgetter(1), reverse=True)]

    # Otherwise add missing values up to 100
    # One cycle is enough, since flooring removes a max value of < 1 per item,
    # i.e. this loop should always break before going through the whole list
    for key in sorted_keys:
        if sum(result.values()) == 100:
            break
        result[key] += 1

    # Return
    return result
0
beruic

vérifier si cela est valide ou non dans la mesure de mes cas de test, je suis capable de faire fonctionner cela.

disons que le nombre est k;

  1. trier le pourcentage par ordre décroissant.
  2. itérer sur chaque pourcentage par ordre décroissant.
  3. calculer le pourcentage de k pour le premier pourcentage prendre Math.Ceil de sortie.
  4. prochain k = k-1
  5. itérer jusqu'à ce que tout le pourcentage est consommé. 
0
lax

Ceci est un cas d'arrondi bancaire, alias «arrondi à moitié égal». Il est supporté par BigDecimal. Son but est d’assurer l’arrondi des soldes, c’est-à-dire qu’il ne favorise ni la banque ni le client.

0
user207421