239

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など)。

4

22 に答える 22

183

元の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から大幅に減少します。

于 2012-11-20T23:02:02.223 に答える
52

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

次に、履歴とともにそれを使用して、使用する値を決定します。たとえば、指定した値を使用すると、次のようになります。

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つの大まかな値のような「問題のある」シーケンスでも機能し、そのうちの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
于 2012-11-20T22:43:54.520 に答える
37

ここでの答えはどれもそれを適切に解決していないようですので、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]
于 2012-11-21T03:27:55.510 に答える
24

丸めの目的は、エラーの発生を最小限に抑えることです。単一の値を四捨五入する場合、そのプロセスは単純で単純であり、ほとんどの人はそれを簡単に理解できます。同時に複数の数値を丸める場合、プロセスは複雑になります。エラーがどのように組み合わされるか、つまり何を最小化する必要があるかを定義する必要があります。

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

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

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

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

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

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

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

from math import isclose, sqrt

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つを四捨五入する必要がありました。相対誤差が最も低いのは、はるかに小さい選択肢ではなく、その大きな値を切り上げた結果です。

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

于 2016-01-23T05:30:01.850 に答える
10

私は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 });
}
于 2016-01-18T00:55:47.967 に答える
9

四捨五入した数値を合計しないでください。不正確な結果になります。項の数と小数部分の分布によっては、合計が大幅に減少する可能性があります。

四捨五入された数値を表示しますが、実際の値を合計します。数字をどのように提示するかによって、実際の方法は異なります。そうすればあなたは

14
 48
 10
 29
 __
100

いずれにせよ、矛盾が生じるでしょう。あなたの例では、1つの値を間違った方法で「四捨五入」せずに合計100になる数値を表示する方法はありません(最小のエラーは9.596を9に変更することです)

編集

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

  1. アイテムの精度
  2. 合計の精度(丸められた値を合計する場合)
  3. 四捨五入された項目と四捨五入された合計の間の一貫性)

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

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

于 2012-11-20T22:53:26.047 に答える
6

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

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

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

于 2012-11-20T22:50:22.153 に答える
3

必要な精度のレベルはわかりませんが、小数の合計の上限である最初の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

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

于 2012-11-20T23:38:46.453 に答える
2

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

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

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

私は通常、MATLABの一般的な問題を、ここでは2進整数計画法ツールである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個の用語の場合、そのスペースはかなりのサイズになります。その場合、おそらく分枝限定法を使用して、スペースを効率的に検索できるツールを使用する必要があります。

于 2012-11-21T00:01:46.540 に答える
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%  |
-------------------------------------------------

基本的に、私の関数の結果は実際に最小のエラーをもたらします。

ここでフィドル

于 2012-11-21T00:11:47.453 に答える
2

注:選択された答えは、好ましくない配列の順序を変更することです。ここでは、同じ結果を達成し、配列の順序を維持する、より多くの異なるバリエーションを提供します

討論

[98.88, .56, .56]どのように丸めたいですか?4つのオプションがあります

1-切り上げて、残りの数値から加算されたものを引くと、結果は次のようになります。[98, 1, 1]

これは良い答えかもしれませんが、もし私たちが持っているとしたらどう[97.5, .5, .5, .5, .5, .5]でしょうか?次に、それを切り上げる必要があります[95, 1, 1, 1, 1, 1]

どうなるかわかりますか?0のような数字をさらに追加すると、残りの数字からより多くの価値が失われます。のようなゼロのような数の大きな配列がある場合、これは非常に厄介になる可能性があります[40, .5, .5 , ... , .5]。これを切り上げると、次のような配列になる可能性があります。[1, 1, .... , 1]

したがって、切り上げは適切なオプションではありません。

2-数値を切り捨てます。したがって、に[98.88, .56, .56]なり[98, 0, 0]、100未満の2になります。すでに0であるものはすべて無視し、その差を最大数に合計します。数字が大きいほど多くなります。

3-前と同じですが、数値を切り捨てますが、小数に基づいて降順で並べ替え、小数に基づいて差分を除算します。これにより、最大の小数が差分を取得します。

4-切り上げますが、追加したものを次の数値に追加します。波のように、追加したものは配列の最後にリダイレクトされます。そう[98.88, .56, .56]なる[99, 0, 1]

これらはどれも理想的ではないため、データの形が崩れることに注意してください。

ここでは、ケース2と3のコードを提供します(ゼロのような数が多い場合、ケースNo.1は実用的ではないため)。それは現代のJsであり、使用するためにライブラリを必要としません

2番目のケース

const v1 = [13.626332, 47.989636, 9.596008, 28.788024];// => [ 14, 48, 9, 29 ]
const v2 = [16.666, 16.666, 16.666, 16.666, 16.666, 16.666] // => [ 17, 17, 17, 17, 16, 16 ] 
const v3 = [33.333, 33.333, 33.333] // => [ 34, 33, 33 ]
const v4 = [33.3, 33.3, 33.3, 0.1] // => [ 34, 33, 33, 0 ]
const v5 = [98.88, .56, .56] // =>[ 100, 0, 0 ]
const v6 = [97.5, .5, .5, .5, .5, .5] // => [ 100, 0, 0, 0, 0, 0 ]

const normalizePercentageByNumber = (input) => {
    const rounded: number[] = input.map(x => Math.floor(x));
    const afterRoundSum = rounded.reduce((pre, curr) => pre + curr, 0);
    const countMutableItems = rounded.filter(x => x >=1).length;
    const errorRate = 100 - afterRoundSum;
    
    const deductPortion = Math.ceil(errorRate / countMutableItems);
    
    const biggest = [...rounded].sort((a, b) => b - a).slice(0, Math.min(Math.abs(errorRate), countMutableItems));
    const result = rounded.map(x => {
        const indexOfX = biggest.indexOf(x);
        if (indexOfX >= 0) {
            x += deductPortion;
            console.log(biggest)
            biggest.splice(indexOfX, 1);
            return x;
        }
        return x;
    });
    return result;
}

3番目のケース

const normalizePercentageByDecimal = (input: number[]) => {

    const rounded= input.map((x, i) => ({number: Math.floor(x), decimal: x%1, index: i }));

    const decimalSorted= [...rounded].sort((a,b)=> b.decimal-a.decimal);
    
    const sum = rounded.reduce((pre, curr)=> pre + curr.number, 0) ;
    const error= 100-sum;
    
    for (let i = 0; i < error; i++) {
        const element = decimalSorted[i];
        element.number++;
    }

    const result= [...decimalSorted].sort((a,b)=> a.index-b.index);
    
    return result.map(x=> x.number);
}

4番目のケース

各ラウンドアップで数値に追加または差し引かれる余分な空気の量を計算し、次の項目で再度加算または減算する必要があります。

const v1 = [13.626332, 47.989636, 9.596008, 28.788024];// => [14, 48, 10, 28 ]
const v2 = [16.666, 16.666, 16.666, 16.666, 16.666, 16.666] // => [17, 16, 17, 16, 17, 17]
const v3 = [33.333, 33.333, 33.333] // => [33, 34, 33]
const v4 = [33.3, 33.3, 33.3, 0.1] // => [33, 34, 33, 0]

const normalizePercentageByWave= v4.reduce((pre, curr, i, arr) => {

    let number = Math.round(curr + pre.decimal);
    let total = pre.total + number;

    const decimal = curr - number;

    if (i == arr.length - 1 && total < 100) {
        const diff = 100 - total;
        total += diff;
        number += diff;
    }

    return { total, numbers: [...pre.numbers, number], decimal };

}, { total: 0, numbers: [], decimal: 0 });
于 2021-02-16T09:42:10.493 に答える
2

オプションが2つしかない場合は、を使用するとよいでしょうMath.round()。問題のある値のペアのみがX.5(たとえば、37.5と62.5)であり、両方の値が切り上げられ、101%ここで試すことができるようになります。

https://jsfiddle.net/f8np1t0k/2/

常に100%を表示する必要があるため、たとえば最初の1つで、それらの1つのパーセンテージを削除するだけです。

const correctedARounded = Number.isInteger(aRounded-0.5) ? a - 1 : a

または、投票率が高いオプションを優先することもできます。

1%の差分のエラーは、1〜100の値のペア間の10kの分割の場合に114回発生します。

于 2021-09-26T14:26:11.050 に答える
2

VarunVohraによるよく投票された回答のための私のJS実装

const set1 = [13.626332, 47.989636, 9.596008, 28.788024];
// const set2 = [24.25, 23.25, 27.25, 25.25];

const values = set1;

console.log('Total: ', values.reduce((accum, each) => accum + each));
console.log('Incorrectly Rounded: ', 
  values.reduce((accum, each) => accum + Math.round(each), 0));

const adjustValues = (values) => {
  // 1. Separate integer and decimal part
  // 2. Store both in a new array of objects sorted by decimal part descending
  // 3. Add in original position to "put back" at the end
  const flooredAndSortedByDecimal = values.map((value, position) => (
    {
        floored: Math.floor(value),
        decimal: value - Number.parseInt(value),
        position
    }
  )).sort(({decimal}, {decimal: otherDecimal}) => otherDecimal - decimal);

  const roundedTotal = values.reduce((total, value) => total + Math.floor(value), 0);
  let availableForDistribution = 100 - roundedTotal;

  // Add 1 to each value from what's available
  const adjustedValues = flooredAndSortedByDecimal.map(value => {
    const { floored, ...rest } = value;
    let finalPercentage = floored;
    if(availableForDistribution > 0){
        finalPercentage = floored + 1;
        availableForDistribution--;
    }

    return {
        finalPercentage,
        ...rest
    }
  });

  // Put back and return the new values
  return adjustedValues
    .sort(({position}, {position: otherPosition}) => position - otherPosition)
    .map(({finalPercentage}) => finalPercentage);
}

const finalPercentages = adjustValues(values);
console.log({finalPercentages})

// { finalPercentage: [14, 48, 9, 29]}
于 2021-10-01T18:42:58.843 に答える
1

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

あなたが持っているNパーセントの小数部分を取ることができます(あなたがそれを与えた例では4です)。

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

分数が最も高い3つの数字を覆い、残りを床にします。

(編集してすみません)

于 2012-11-20T22:44:07.637 に答える
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日目についてはわかりません。ですから、あなたが本当に、本当に、近似しなければならないのでない限り、あなたはおそらくそうではない方が良いでしょう。

于 2016-12-28T13:13:58.410 に答える
1

または、簡潔にするためにこのようなもので、エラーを累積するだけです...

const p = [13.626332, 47.989636, 9.596008, 28.788024];
const round = (a, e = 0) => a.map(x => (r = Math.round(x + e), e += x - r, r));
console.log(round(p));

結果:[14、48、9、29]

于 2021-11-04T02:41:09.923 に答える
0

これが有効かどうかを確認してください。私のテストケースでは、これを機能させることができます。

数がkだとしましょう。

  1. 降順でパーセンテージを並べ替えます。
  2. 降順から各パーセンテージを繰り返します。
  3. 最初のパーセンテージのkのパーセンテージを計算し、出力のMath.Ceilを取得します。
  4. 次のk=k-1
  5. すべてのパーセンテージが消費されるまで繰り返します。
于 2017-07-31T06:50:50.903 に答える
0

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

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
于 2017-09-13T22:34:47.657 に答える
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

、、が必要mathです。itertoolsoperator

于 2018-04-12T05:56:40.947 に答える
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
于 2020-01-14T16:16:56.070 に答える
0

最大剰余方式を実装するRubygemは次のとおりです: https ://github.com/jethroo/lare_round

使用するには:

a =  Array.new(3){ BigDecimal('0.3334') }
# => [#<BigDecimal:887b6c8,'0.3334E0',9(18)>, #<BigDecimal:887b600,'0.3334E0',9(18)>, #<BigDecimal:887b4c0,'0.3334E0',9(18)>]
a = LareRound.round(a,2)
# => [#<BigDecimal:8867330,'0.34E0',9(36)>, #<BigDecimal:8867290,'0.33E0',9(36)>, #<BigDecimal:88671f0,'0.33E0',9(36)>]
a.reduce(:+).to_f
# => 1.0
于 2020-12-31T03:24:09.337 に答える
-1

これは、銀行家の丸め、別名「丸め半偶数」の場合です。BigDecimalでサポートされています。その目的は、四捨五入のバランスをとること、つまり銀行にも顧客にも有利にならないようにすることです。

于 2012-11-20T23:03:20.407 に答える