Come fare percentuali arrotondate aggiungere fino al 100%

Considera le quattro percentuali seguenti, rappresentate come numeri float :

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

Devo rappresentare queste percentuali come numeri interi. Se uso semplicemente Math.round() , Math.round() con un totale del 101%.

 14 + 48 + 10 + 29 = 101 

Se uso parseInt() , parseInt() con un totale del 97%.

 13 + 47 + 9 + 28 = 97 

Qual è un buon algoritmo per rappresentare un numero qualsiasi di percentuali come numeri interi pur mantenendo un totale del 100%?


Modifica : dopo aver letto alcuni commenti e risposte, ci sono chiaramente molti modi per risolvere questo problema.

Nella mia mente, per rimanere fedele ai numeri, il risultato “giusto” è quello che minimizza l’errore complessivo, definito dalla quantità di arrotondamento degli errori introdotta rispetto al valore effettivo:

  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) 

In caso di parità (3.33, 3.33, 3.33) può essere presa una decisione arbitraria (ad es. 3, 4, 3).

Poiché nessuna delle risposte qui sembra risolverla correttamente, ecco la mia versione semi-offuscata usando 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] 

Ci sono molti modi per fare proprio questo, a patto di non essere preoccupati di fare affidamento sui dati decimali originali.

Il primo e forse il metodo più popolare sarebbe il metodo di restringimento più grande

Che è fondamentalmente:

  1. Arrotondando tutto
  2. Ottenere la differenza in sum e 100
  3. Distribuire la differenza aggiungendo 1 agli articoli in ordine decrescente delle loro parti decimali

Nel tuo caso, sarebbe come questo:

 13.626332% 47.989636% 9.596008% 28.788024% 

Se prendi le parti intere, ottieni

 13 47 9 28 

che aggiunge fino a 97 e si desidera aggiungerne altri tre. Ora, guardi le parti decimali, quali sono

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

e prendi quelli più grandi fino a raggiungere il totale 100. Quindi otterresti:

 14 48 9 29 

In alternativa, puoi semplicemente scegliere di mostrare una cifra decimale anziché i valori interi. Quindi i numeri sarebbero 48,3 e 23,9, ecc. Questo diminuirebbe la varianza da 100 di molto.

Probabilmente il modo “migliore” per farlo è di mantenere un conteggio (non integrale) in esecuzione di dove sei e di arrotondare quel valore, quindi usarlo insieme alla cronologia per capire quale valore dovrebbe essere usato. Ad esempio, utilizzando i valori che hai dato:

 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 

In ogni fase, non arrotondare il numero stesso. Invece, arrotondate il valore accumulato e calcolate il numero intero migliore che raggiunge quel valore dalla linea di base precedente – quella linea di base è il valore cumulativo (arrotondato) della riga precedente.

Funziona perché non stai perdendo informazioni in ogni fase ma piuttosto utilizzando le informazioni in modo più intelligente. I valori arrotondati ‘corretti’ sono nella colonna finale e puoi vedere che sumno a 100.

L’objective dell’arrotondamento è generare il minor numero di errori. Quando arrotondi un singolo valore, quel processo è semplice e diretto e la maggior parte delle persone lo capisce facilmente. Quando si arrotondano più numeri contemporaneamente, il processo diventa più complicato: è necessario definire in che modo combinare gli errori, ovvero cosa deve essere ridotto a icona.

La buona risposta di Varun Vohra riduce al minimo la sum degli errori assoluti ed è molto semplice da implementare. Tuttavia ci sono casi limite che non gestisce: quale dovrebbe essere il risultato dell’arrotondamento 24.25, 23.25, 27.25, 25.25 ? Uno di questi deve essere arrotondato anziché abbassato. Probabilmente sceglieresti solo il primo o l’ultimo nell’elenco.

Forse è meglio usare l’errore relativo al posto dell’errore assoluto . L’arrotondamento da 23,25 a 24 lo modifica del 3,2% mentre l’arrotondamento da 27,25 a 28 lo modifica solo del 2,8%. Ora c’è un chiaro vincitore.

È ansible modificare ulteriormente questo aspetto. Una tecnica comune consiste nel quadrare ogni errore, in modo che gli errori più grandi contano in modo sproporzionato rispetto a quelli piccoli. Userei anche un divisore non lineare per ottenere l’errore relativo – non sembra giusto che un errore all’1% sia 99 volte più importante di un errore al 99%. Nel codice qui sotto ho usato la radice quadrata.

L’algoritmo completo è il seguente:

  1. Somma le percentuali dopo averle arrotondate tutte e sottratte da 100. Questo ti dice quante di queste percentuali devono essere arrotondate per eccesso.
  2. Generare due punteggi di errore per ogni percentuale, uno quando arrotondato per difetto e uno quando arrotondato per eccesso. Prendi la differenza tra i due.
  3. Ordina le differenze di errore prodotte sopra.
  4. Per il numero di percentuali che devono essere arrotondate, prendere un object dall’elenco ordinato e incrementare la percentuale arrotondata di 1.

Potresti ancora avere più di una combinazione con la stessa sum di errore, ad esempio 33.3333333, 33.3333333, 33.3333333 . Questo è inevitabile e il risultato sarà completamente arbitrario. Il codice che do sotto preferisce arrotondare i valori a sinistra.

Mettere tutto insieme in Python assomiglia a questo.

 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] 

Come puoi vedere con l’ultimo esempio, questo algoritmo è ancora in grado di fornire risultati non intuitivi. Anche se 89.0 non ha bisogno di arrotondamenti di sorta, uno dei valori in tale elenco doveva essere arrotondato per eccesso; l’errore relativo più basso deriva dall’arrotondamento di quel grande valore piuttosto che delle alternative molto più piccole.

Questa risposta originariamente sosteneva di passare attraverso ogni ansible combinazione di arrotondamento / arrotondamento verso il basso, ma come sottolineato nei commenti un metodo più semplice funziona meglio. L’algoritmo e il codice riflettono questa semplificazione.

NON sumre i numeri arrotondati. Avrai risultati inaccurati. Il totale potrebbe essere notevolmente inferiore a seconda del numero di termini e della distribuzione delle parti frazionali.

Visualizza i numeri arrotondati ma sum i valori effettivi. A seconda di come stai presentando i numeri, il modo effettivo per farlo potrebbe variare. In questo modo ottieni

  14
  48
  10
  29
  __
 100 

In qualsiasi modo tu abbia discrepanza. Non c’è modo nel tuo esempio di mostrare numeri che aggiungono fino a 100 senza “arrotondare” un valore nel modo sbagliato (il minimo errore cambierebbe da 9.596 a 9)

MODIFICARE

Devi scegliere tra uno dei seguenti:

  1. Precisione degli articoli
  2. Precisione della sum (se stai sumndo valori arrotondati)
  3. Coerenza tra gli elementi arrotondati e la sum arrotondata)

La maggior parte delle volte quando si ha a che fare con le percentuali # 3 è l’opzione migliore perché è più ovvio quando il totale è pari al 101% rispetto a quando i singoli elementi non sono pari a 100 e si mantengono i singoli elementi precisi. “Arrotondare” da 9.596 a 9 è impreciso secondo me.

Per spiegare questo, a volte aggiungo una nota a piè di pagina che spiega che i singoli valori sono arrotondati e potrebbero non essere totali al 100% – chiunque comprenda l’arrotondamento dovrebbe essere in grado di comprendere quella spiegazione.

Ho scritto un aiutante di arrotondamento in versione C #, l’algoritmo è lo stesso della risposta di Varun Vohra , spero che aiuti.

 public static List GetPerfectRounding(List 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 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; } 

Supera il seguente test unitario:

 [TestMethod] public void TestPerfectRounding() { CollectionAssert.AreEqual(Utils.GetPerfectRounding( new List {3.333m, 3.334m, 3.333m}, 10, 2), new List {3.33m, 3.34m, 3.33m}); CollectionAssert.AreEqual(Utils.GetPerfectRounding( new List {3.33m, 3.34m, 3.33m}, 10, 1), new List {3.3m, 3.4m, 3.3m}); CollectionAssert.AreEqual(Utils.GetPerfectRounding( new List {3.333m, 3.334m, 3.333m}, 10, 1), new List {3.3m, 3.4m, 3.3m}); CollectionAssert.AreEqual(Utils.GetPerfectRounding( new List { 13.626332m, 47.989636m, 9.596008m, 28.788024m }, 100, 0), new List {14, 48, 9, 29}); CollectionAssert.AreEqual(Utils.GetPerfectRounding( new List { 16.666m, 16.666m, 16.666m, 16.666m, 16.666m, 16.666m }, 100, 0), new List { 17, 17, 17, 17, 16, 16 }); CollectionAssert.AreEqual(Utils.GetPerfectRounding( new List { 33.333m, 33.333m, 33.333m }, 100, 0), new List { 34, 33, 33 }); CollectionAssert.AreEqual(Utils.GetPerfectRounding( new List { 33.3m, 33.3m, 33.3m, 0.1m }, 100, 0), new List { 34, 33, 33, 0 }); } 

Potresti provare a tenere traccia del tuo errore a causa dell’arrotondamento e quindi arrotondare contro il grano se l’errore accumulato è maggiore della porzione frazionaria del numero corrente.

 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 

Non sono sicuro se questo funzionerebbe in generale, ma sembra funzionare in modo simile se l’ordine è invertito:

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

Sono sicuro che ci sono casi limite in cui questo potrebbe fallire, ma qualsiasi approccio sarà almeno in qualche modo arbitrario poiché stai praticamente modificando i tuoi dati di input.

Una volta ho scritto uno strumento senza bordi, per trovare la minima perturbazione su un insieme di numeri per abbinare un objective. Era un problema diverso, ma in teoria si potrebbe usare un’idea simile qui. In questo caso, abbiamo una serie di scelte.

Quindi, per il primo elemento, possiamo arrotondarlo a 14 o giù fino a 13. Il costo (in un senso di programmazione di numeri interi) di farlo è inferiore per il round up rispetto al round down, perché il round down richiede sposta quel valore a una distanza maggiore. Allo stesso modo, possiamo arrotondare ogni numero verso l’alto o verso il basso, quindi ci sono un totale di 16 scelte da cui scegliere.

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

Normalmente risolverei il problema generale in MATLAB, usando qui bintprog, uno strumento di programmazione di numeri interi binari, ma ci sono solo alcune scelte da testare, quindi è abbastanza facile con semplici cicli di test di ciascuna delle 16 alternative. Ad esempio, supponiamo di arrotondare questo set come:

  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’errore assoluto totale effettuato è 1.25266. Può essere leggermente ridotto con il seguente arrotondamento alternativo:

  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 

In effetti, questa sarà la soluzione ottimale in termini di errore assoluto. Naturalmente, se ci fossero 20 termini, lo spazio di ricerca sarà di dimensione 2 ^ 20 = 1048576. Per 30 o 40 termini, lo spazio sarà di dimensioni significative. In tal caso, è necessario utilizzare uno strumento in grado di cercare in modo efficiente lo spazio, magari utilizzando uno schema derivato e associato.

Penso che quanto segue realizzerà ciò che stai cercando

 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. eg 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] 

Un’ultima cosa, ho eseguito la funzione utilizzando i numeri originariamente indicati nella domanda per confrontare con l’output desiderato

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

Questo era diverso da ciò che la domanda voleva => [48, 29, 14, 9]. Non sono riuscito a capirlo fino a quando non ho osservato il margine di errore 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% | ------------------------------------------------- 

In sostanza, il risultato della mia funzione introduce effettivamente la minima quantità di errore.

Fiddle qui

Se lo stai arrotondando non c’è un buon modo per ottenere esattamente lo stesso in tutti i casi.

Puoi prendere la parte decimale delle percentuali N che hai (nell’esempio che hai dato è 4).

Aggiungi le parti decimali. Nel tuo esempio hai totale della parte frazionaria = 3.

Ceil i 3 numeri con le frazioni più alte e terra il resto.

(Scusa per le modifiche)

Non sono sicuro di quale livello di precisione ti serva, ma quello che farei è semplicemente aggiungere 1 i primi n numeri, n essendo il ceil della sum totale dei decimali. In questo caso è 3 , quindi aggiungo 1 ai primi 3 elementi e piano resto. Ovviamente questo non è molto accurato, alcuni numeri potrebbero essere arrotondati per eccesso o per difetto quando non dovrebbe, ma funziona bene e risulterà sempre al 100%.

Quindi [ 13.626332, 47.989636, 9.596008, 28.788024 ] sarebbe [14, 48, 10, 28] perché 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 

Puoi sempre informare gli utenti che i numeri sono arrotondati e potrebbero non essere super-accurati …

Se devi davvero aggirarli, ci sono già dei suggerimenti molto validi qui (il resto più grande, l’errore relativo minore e così via).

C’è già un buon motivo per non arrotondare (otterrete almeno un numero che “sembra migliore” ma è “sbagliato”), e come risolverlo (avvertite i vostri lettori) ed è quello che faccio.

Lasciatemi aggiungere la parte del numero “sbagliato”.

Supponiamo di avere tre eventi / quadro / … con alcune percentuali approssimative come:

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

Più tardi i valori cambiano leggermente, a

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

La prima tabella ha il già citato problema di avere un numero “sbagliato”: 33.34 è più vicino a 33 che a 34.

Ma ora hai un errore più grande. Confrontando il giorno 2 con il giorno 1, il valore percentuale reale per A è aumentato, dello 0,01%, ma l’approssimazione mostra una diminuzione dell’1%.

Questo è un errore qualitativo, probabilmente molto peggio dell’errore quantitativo iniziale.

Si potrebbe escogitare un’approssimazione per l’intero set ma, potrebbe essere necessario pubblicare i dati il ​​primo giorno, quindi non si saprà nulla del secondo giorno. Quindi, a meno che tu davvero, davvero, debba approssimarsi, probabilmente no.

Questo è un caso per l’arrotondamento del banchiere, alias “round half-even”. È supportato da BigDecimal. Il suo scopo è quello di assicurare che l’arrotondamento saldi, cioè non favorisca né la banca né il cliente.

controlla se questo è valido o meno per quanto riguarda i miei casi di test che sono in grado di farlo funzionare.

diciamo che il numero è k;

  1. ordina la percentuale decrescente o.
  2. scorrere su ogni percentuale dall’ordine decrescente.
  3. calcola la percentuale di k per la prima percentuale prendi Math.Ceil di output.
  4. next k = k-1
  5. itera fino a quando non viene consumata tutta la percentuale.

Ho implementato il metodo dalla risposta di Varun Vohra qui sia per gli elenchi che per i ditt.

 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, # ie 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 

Ecco un’implementazione Python più semplice della risposta di @ 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 

Hai bisogno di math , itertools , operator .