四捨五入したパーセンテージを合計すると100%になる方法


192

float数値で表された以下の4つのパーセンテージを検討してください。

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

これらの割合を整数で表す必要があります。単純にを使用Math.round()すると、合計101%になります。

14 + 48 + 10 + 29 = 101

を使用parseInt()すると、合計97%になります。

13 + 47 + 9 + 28 = 97

合計として100%を維持しながら、任意の数のパーセンテージを整数として表すための適切なアルゴリズムは何ですか?


編集:コメントと回答の一部を読んだ後、これを解決するための方法は明らかにたくさんあります。

私の考えでは、数値に忠実であり続けるために、「正しい」結果は、実際の値に対してどれだけのエラー丸めが導入するかによって定義される、全体的なエラーを最小化するものです。

        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)

同数の場合(3.33、3.33、3.33)、任意の決定を行うことができます(たとえば、3、4、3)。


21
3.33、3.33、3.33があるとします。どれを4にしますか?
RobG、2012年

3
丁度。この質問は、言葉の矛盾を体現しています。
ローンの侯爵2012年

4
これは、レポートで非常に一般的なシナリオです。表示された値の合計と常に一致するとは限らない10進数値の「合計」を表示する方法です。
Dスタンリー

1
あなたの例の場合の「正しい」結果は何ですか?これにより、「最良の」ソリューションとは何かに関する不一致が解決される場合があります。
Dスタンリー

回答:


35

ここでの答えはどれも適切に解決していないようですので、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]

6
私が間違っている場合は修正しますが、これは私の回答で提案されたアルゴリズムの実装ではありませんか?(underscorejsではクリアしないでください)
vvohra87

@VarunVohraすみません、これまで気づかなかったので、はい、アルゴリズムは同じように見えます:)なぜ私の投稿が承認された答えであるのかわからない、難読化されたコードはlolzのためだけだった...
yonilevy

@yonilevyはコメントを削除しました。ソートされたリストを返すことになっていることに気づかなかっただけです。謝罪します!
ザックバート

2
最後の要素が0で、前の要素が100になると、この関数に問題があります。例:[52.6813880126183、5.941114616193481、24.55310199789695、8.780231335436383、8.04416403785489、0]。最後のものは論理的に-1を返します。私は次の解決策を本当にすぐに考えましたが、おそらくもっと良いものがあるでしょう:jsfiddle.net/0o75bw43/1
Cruclax

1
@Cruclaxは、入力配列のすべてのエントリがゼロのときにすべて1を示します
tony.0919

158

元の10進データへの依存を気にしない限り、これを行う方法はたくさんあります。

最初の、おそらく最も一般的な方法は、最大の剰余法です。

基本的には次のとおりです。

  1. すべてを切り捨て
  2. 合計と100の差を取得する
  3. 小数部の降順でアイテムに1を追加して差を分配する

あなたの場合、それはこのようになります:

13.626332%
47.989636%
 9.596008%
28.788024%

整数部分を取ると、

13
47
 9
28

合計で97になり、さらに3つ追加したいとします。今、あなたは小数部を見て、それは

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

合計が100になるまで最大のものを使用します。つまり、次のようになります。

14
48
 9
29

または、整数値の代わりに小数点以下1桁を表示するように選択することもできます。したがって、数値は48.3や23.9などになります。これにより、分散が100から大幅に減少します。


5
American Mathematical SocietyのWebサイトにあるこの「機能コラム」– 按分II:按分システム –は、いくつかの同様の「按分」方法について説明しています。
ケニーエビット2013年

1
これは、私の回答のコピーアンドペーストのようなものです。stackoverflow.com/ questions / 5227215 /…です。
さわ

@DStanleyの回答に関するコメントとは異なり、回答では9.596008%が9%に丸められ、0.5%を超える差があることに注意してください。しかし、それでも良い答えです。
Rolazaro Azeveires '16年

32

おそらく、これを行うための「最良の」方法(「最良」は主観的な用語であるため引用)は、現在の位置を継続的(非整数)に集計し、その値を丸めることです。

次に、それを履歴とともに使用して、どの値を使用する必要があるかを調べます。たとえば、指定した値を使用します。

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

各段階で、数値自体を丸めません。代わりに、丸め蓄積最良整数外れ値及び作業をその前のベースラインからの値がその到達-そのベースラインが前の行の累積値(四捨五入)です。

あなたがしているので、これは動作しますない各段階で情報を失うことではなく、よりインテリジェントな情報を使用して。「正しい」丸められた値が最後の列にあり、合計が100であることがわかります。

これと上の値の3番目の値で、各値を盲目的に丸めることの違いを確認できます。一方では9.596008、通常のラウンドアップに相当するであろう10、蓄積が71.211976正しくに切り捨て71のみ、この手段- 9の前のベースラインに追加する必要があります62


また、これは3つのroughly-のような「問題」シーケンスのために働く値は、1それらの切り上げする必要があります。1/3

Value      CumulValue  CumulRounded  PrevBaseline  Need
---------  ----------  ------------  ------------  ----
                                  0
33.333333   33.333333            33             0    33 ( 33 -  0)
33.333333   66.666666            67            33    34 ( 67 - 33)
33.333333   99.999999           100            67    33 (100 - 67)
                                                    ---
                                                    100

1
2番目のアプローチは、これらの問題の両方を修正します。最初が与える26, 25, 26, 23、2番目1, 0, 1, 0, 1, 0, ...
paxdiablo 2015

それが負の数の罪に出力防ぐように、このアプローチはまた、小さな数字を丸めに適しています
Jonty5817

18

丸めの目的は、最小のエラーを生成することです。単一の値を丸める場合、そのプロセスは単純で簡単で、ほとんどの人は簡単に理解できます。複数の数値を同時に丸める場合、プロセスはより複雑になります。エラーを組み合わせる方法、つまり最小化する必要があるものを定義する必要があります。

VARUN Vohra著によるだけでなく、投票の答えは絶対誤差の総和を最小化し、それが実現するのは非常に簡単です。しかし、それが処理しないエッジケースがあります-丸めの結果はどうあるべき24.25, 23.25, 27.25, 25.25ですか?それらの1つは、切り捨てるのではなく、切り上げる必要があります。おそらく、リストの最初または最後のものを任意に選択するだけです。

おそらく、絶対誤差の代わりに相対誤差を使用する方が良いでしょう。23.25を24に丸めると、3.2%変化しますが、27.25を28に丸めると、2.8%しか変化しません。今、明確な勝者がいます。

これをさらに微調整することは可能です。一般的な手法の1つは、各エラーを二乗することです。これにより、大きなエラーは小さなエラーよりも不釣り合いに多くカウントされます。また、相対除数を取得するために非線形除数を使用します。1%のエラーが99%のエラーよりも99倍重要であることは正しくないようです。以下のコードでは、平方根を使用しています。

完全なアルゴリズムは次のとおりです。

  1. すべての端数を切り捨てた後、パーセンテージを合計し、100から差し引きます。これにより、それらのパーセンテージのうち、いくつが切り上げられる必要があるかがわかります。
  2. パーセンテージごとに、切り捨て時と切り上げ時の2つのエラースコアを生成します。2つの違いを理解してください。
  3. 上記で生成されたエラーの違いを並べ替えます。
  4. 切り上げが必要なパーセンテージの数については、ソートされたリストから項目を1つ取り、切り捨てられたパーセンテージを1増やします。

たとえば、同じエラー合計の組み合わせが複数ある場合もあります33.3333333, 33.3333333, 33.3333333。これは避けられず、結果は完全に恣意的なものになります。以下に示すコードでは、左側の値を切り上げることを推奨しています。

すべてをPythonにまとめると、次のようになります。

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]

この最後の例でわかるように、このアルゴリズムは依然として直感的でない結果を提供することができます。89.0は丸めをまったく必要としませんが、そのリストの値の1つを切り上げる必要がありました。最も低い相対誤差は、はるかに小さい代替値ではなく、その大きな値を切り上げた結果です。

この回答は当初、切り上げ/切り捨ての可能なすべての組み合わせを検討することを提唱していましたが、コメントで指摘されているように、単純な方法の方が効果的です。アルゴリズムとコードはその単純化を反映しています。


1
プロセスを行くから、加重誤差の低下の減少のために:私はあなたがすべての組み合わせを考慮する必要がないと思うゼロにラウンド無限大にラウンドを(ほとんどただ導入計量Verun Vohrasさんyonilevyさん(「同一」)の回答)。
greybeard 2016年

@greybeardあなたが正しい、私はこれを考えすぎていた。各値に2つのエラーがあるため、エラーを並べ替えることはできませんでしたが、差をとることで問題が解決しました。答えを更新しました。
Mark Ransom

実際の数が0%のときは、常に0%を使用することを好みます。したがって、に追加if actual == 0: return 0すると効果error_gen的です。
Nikolay Baluk 16

1
isclose最初のメソッド は何round_to_100ですか?
toto_tico 2018年


7

丸めた数値を合計しないでください。結果は不正確になります。項の数と小数部の分布によっては、合計が大幅にずれる場合があります。

丸められた数値を表示しますが、実際の値を合計します。数値の表示方法によって、実際の方法は異なります。そうすれば

 14
 48
 10
 29日
 __
100

どこへ行っても矛盾が生じます。あなたの例では、1つの値を間違って「丸め」ずに、合計が100になる数値を表示する方法はありません(最小のエラーは9.596を9に変更することになります)

編集

次のいずれかを選択する必要があります。

  1. アイテムの精度
  2. 合計の精度(丸めた値を合計する場合)
  3. 丸められた項目と丸められた合計の間の整合性)

パーセンテージ#3を扱う場合は、ほとんどの場合、合計が101%の方が、個々のアイテムの合計が100にならない場合よりも明白であり、個々のアイテムを正確に保つため、最良のオプションです。「丸め」9.596から9は、私の意見では不正確です。

これを説明するために、個々の値が丸められ、合計が100%にならない可能性があることを説明する脚注を追加することがあります。丸めを理解している人なら誰でもその説明を理解できるはずです。


6
印刷された値の合計が100にならないため、それほど役に立ちません。質問の目的は、ユーザーが値が間違っていると考えないようにすることでした。この場合、ほとんどの人は合計を見て比較すると、 。
vvohra87

@VarunVohra私の編集を読んでください。0.5を1で「四捨五入」しないと、合計が100になるような数値を表示することはできません。
Dスタンリー

1
@DStanleyは、実際には、すべての数値が0.5の恥ずかしがり屋であるセットを除いて、できます。私の答えを確認してください-LRMはまさにそれを行います。
vvohra87 2012年

3
@VarunVohra元の例では、LRMは14、48、9、29を生成し、9.596から9を「四捨五入」します。整数に基づいて割り当てている場合、LRMが最も正確ですが、1つの結果がさらに変化します。ハーフユニットより。
Dスタンリー

7

私はC#バージョンの丸めヘルパーを作成しました。アルゴリズムはVarun Vohraの回答と同じです。

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

次の単体テストに合格します。

[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 });
}

いいね!最初に土台を提供してくれました
。Enumerable

4

丸めによるエラーを追跡し、累積されたエラーが現在の数値の小数部分より大きい場合は、粒度に対して丸めることを試みることができます。

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

これが一般的に機能するかどうかはわかりませんが、順序を逆にしても同様に機能するようです:

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

これが失敗する可能性のあるエッジケースがあると私は確信していますが、基本的に入力データを変更しているため、どのアプローチも少なくともいくぶん恣意的なものになります。


2
会計士と銀行家は何百年もの間同様の技術を使用してきました。"残りを運ぶ"ある行から次の行へ。「キャリー」の1セントの1/2から始めます。「キャリー」を最初の値に追加し、切り捨てます。次に、切り捨てによって失われた量を「キャリー」に入れます。これを一番下まで行うと、丸められた数値が毎回正確に合計されて希望の合計になります。
ジェフグリッグ2017年

Carolyn Kayは、Access VB 2007でのこの実装を提案しました:<code> '「残りを運ぶ」メソッドを使用したドルの払い戻しref1 = rsQry![Refund Paid $$$] * rsQry![Property Value] / propValTot ref2 = ref1 + ref5 'キャリー剰余を追加して開始するにはゼロref3 = ref2 * 100' 100で整数に乗算ref4 = ref3 / 100 '100で除算して10進数にrsTbl![Refund Paid $$$] = ref4' Put the " 「残り」テーブルで丸めた数値ref5 = ref2-ref4 '新しい残りを運ぶ</ code>
ジェフグリッグ

2

私はかつて、目標に一致する一連の数値への最小限の摂動を見つけるために、非丸めツールを作成しました。それは別の問題でしたが、理論的にはここで同様のアイデアを使用できます。この場合、選択肢がいくつかあります。

したがって、最初の要素については、14に切り上げるか、13に切り捨てることができます。切り捨てには、2進整数プログラミングの意味で、切り捨てよりも切り上げの方がコストが低くなります。その値をより大きな距離に移動します。同様に、各数値を切り上げまたは切り捨てることができるため、合計16の選択肢から選択する必要があります。

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

私は通常、MATLABの一般的な問題を解決しますが、ここではバイナリ整数プログラミングツールであるbintprogを使用しますが、テストする選択肢はほとんどないため、単純なループを使用して16の選択肢のそれぞれをテストするのは簡単です。たとえば、このセットを次のように丸めると仮定します。

 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

絶対誤差の合計は1.25266です。次の代替丸めにより、わずかに削減できます。

 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

実際、これは絶対誤差の点で最適なソリューションになります。もちろん、20個の用語がある場合、検索スペースのサイズは2 ^ 20 = 1048576になります。30個または40個の用語の場合、そのスペースはかなりのサイズになります。その場合、おそらくブランチとバインドされたスキームを使用して、スペースを効率的に検索できるツールを使用する必要があります。


将来の参照用に:「最大の剰余」アルゴリズムは、メトリックに従って合計絶対誤差を最小化する必要があります(@varunvohraの回答を参照)。証明は簡単です。エラーを最小限に抑えないとします。次に、切り捨てる値のセットと切り上げる値のセットが必要です(2つのセットは同じサイズです)。ただし、切り捨てるすべての値は、切り上げるすべての値(およびvv)よりも次の整数から離れているため、新しいエラーの量は大きくなるはずです。QED。ただし、すべてのエラーメトリックに対して機能するわけではありません。他のアルゴリズムが必要です。
rici

2

以下はあなたが求めているものを達成すると思います

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]

最後に、質問で最初に与えられた数値を使用して関数を実行し、目的の出力と比較しました

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

これは、質問の内容とは異なりました=> [48、29、14、9]。エラーの総マージンを見るまで、これは理解できませんでした

-------------------------------------------------
| 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%  |
-------------------------------------------------

本質的に、私の関数の結果は、実際には最小限のエラーを引き起こします。

ここをいじる


それは私が念頭に置いていたものとほぼ同じですが、誤差は値と比較して測定する必要があるという違いがあります(9.8から10に丸めると、19.8から20に丸めるよりも大きなエラーになります)。これは、並べ替えのコールバックに反映することで簡単に行えます。
poezn

これは[33.33、33.33、33.33、0.1]では間違っており、より正確な[
34、33、33、0

@yonilevyありがとうございます。今修正されました。
ブルーノ

まだ、[16.666、16.666、16.666、16.666、16.666、16.666]の場合、[16、16、17、17、17、17、17]ではなく[15、17、17、17、17、17、17]を返します。回答
yonilevy

2

どのレベルの精度が必要かはわかりませんが、最初のn数値に1を加算するだけnで、小数の合計の上限になります。この場合は3なので、最初の3つのアイテムに1を追加し、残りをフロアします。もちろん、これは非常に正確ではありません。一部の数値は、正しくない場合は切り上げまたは切り捨てられる可能性がありますが、問題なく機能し、常に100%になります。

そう[ 13.626332, 47.989636, 9.596008, 28.788024 ]だろう[14, 48, 10, 28]ので、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

数値は四捨五入されており、非常に正確ではない可能性があることを常にユーザーに通知できます...


1

あなたがそれを丸めているならば、それをすべての場合に正確に同じにする良い方法はありません。

Nパーセンテージの小数部を取ることができます(例では4です)。

小数部を追加します。あなたの例では、小数部の合計= 3があります。

端数が最も高い3つの数値をCeilし、残りをフロアします。

(編集して申し訳ありません)


1
それは100に追加する番号を提供することができるが、あなたは26に3に3.9と25.1を回し終わる可能性
RobG

番号。3.9は4になり、25.1は25になります。私は、最高の値ではなく、最高の端数を持つ3つの数値を上限としています。
arunlalam

2
.9で終​​わる分数が多すぎる場合、9つの値が9.9%、1つの値が10.9の場合、1つの値が9%、8が10%、1つが11%になります。
arunlalam

1

本当にそれらを丸める必要がある場合は、すでに非常に良い提案があります(最大の剰余、最小の相対誤差など)。

また、丸めない1つの正当な理由(「見た目が良い」が「間違っている」という数値が少なくとも1つ表示されます)と、それを解決する方法(読者に警告)があり、それが私が行うことです。

「間違った」番号の部分を付け加えましょう。

次のように近似するいくつかのパーセンテージを持つ3つのイベント/エンティティ/ ...があるとします。

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

後で値がわずかに変化し、

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

最初のテーブルには、「間違った」数を持つという前述の問題があります。33.34は、34よりも33に近いです。

しかし、今はより大きなエラーがあります。2日目と1日目を比較すると、Aの実際のパーセンテージ値は0.01%増加しましたが、概算では1%減少しています。

これは定性的なエラーであり、おそらく最初の定量的なエラーよりもかなり悪いものです。

セット全体の概算を考案することもできますが、1日目にデータを公開する必要がある場合があるため、2日目についてはわかりません。したがって、本当に、本当に、概算しなければならない場合を除いて、多分、そうしない方が良いでしょう。


より良いテーブルを作成する方法を知っている人は、編集するか、方法/場所を教えてください
Rolazaro Azeveires

0

これが有効であるかどうかを、私のテストケースで確認できます。

数がkであるとしましょう。

  1. 降順でパーセントをソートします。
  2. 降順で各パーセンテージを反復します。
  3. 最初のパーセンテージのkのパーセンテージを計算して、出力のMath.Ceilを取得します。
  4. 次のk = k-1
  5. すべての割合が消費されるまで繰り返します。

0

リストと辞書の両方について、ここでVarun Vohraの回答からメソッドを実装しました。

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

@ varun-vohra回答の簡単なPython実装は次のとおりです。

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

あなたは必要mathitertoolsoperator


0

パンダシリーズのパーセンテージを持っている人のために、これが最大の剰余法の実装です(Varun Vohraの回答のように)。丸めたい小数を選択することもできます。

import numpy as np

def largestRemainderMethod(pd_series, decimals=1):

    floor_series = ((10**decimals * pd_series).astype(np.int)).apply(np.floor)
    diff = 100 * (10**decimals) - floor_series.sum().astype(np.int)
    series_decimals = pd_series - floor_series / (10**decimals)
    series_sorted_by_decimals = series_decimals.sort_values(ascending=False)

    for i in range(0, len(series_sorted_by_decimals)):
        if i < diff:
            series_sorted_by_decimals.iloc[[i]] = 1
        else:
            series_sorted_by_decimals.iloc[[i]] = 0

    out_series = ((floor_series + series_sorted_by_decimals) / (10**decimals)).sort_values(ascending=False)

    return out_series

-1

これは、銀行家の丸め、別名「丸め半偶数」の場合です。BigDecimalでサポートされています。その目的は、四捨五入のバランスをとることを保証することです。つまり、銀行も顧客も優先しません。


5
丸めのバランスが取れていることは保証されません。偶数と奇数の間で半丸めを分散することにより、エラーの量を減らすだけです。銀行家の丸めが不正確な結果をもたらすシナリオはまだあります。
Dスタンリー

@DStanley同意する。私は他に言わなかった。その目的を述べた。非常に慎重に。
ローンの侯爵

2
十分に公正です-私はあなたが言っていることを誤解しました。どちらの場合でも、バンカーの丸めを使用しても例の結果は変更されないため、問題が解決するとは思いません。
Dスタンリー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.