반올림 백분율을 100 %까지 만드는 방법


192

float숫자로 표시된 아래의 네 가지 백분율을 고려하십시오 .

    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

3
바로 그거죠. 이 질문은 용어의 모순을 구현합니다.
Lorne의 후작

4
보고에서 매우 일반적인 시나리오입니다. 표시되는 값의 합계와 항상 일치하지 않는 10 진수 값의 "총"을 표시하는 방법입니다.
D Stanley

1
귀하의 사례에서 "올바른" 결과 는 무엇입니까 ? "최상의"솔루션에 대한 의견 불일치를 해결할 수 있습니다.
D Stanley

답변:


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 내 댓글을 삭제했습니다. 나는 그것이 정렬 된 목록을 반환해야한다는 것을 깨닫지 못했습니다. 죄송합니다!
Zack Burt

2
마지막 요소가 0이고 이전 요소가 100에 추가 될 때이 기능에 문제가 있습니다. 예 : [52.6813880126183, 5.941114616193481, 24.55310199789695, 8.780231335436383, 8.04416403785489, 0]. 마지막 것은 논리적으로 -1을 반환합니다. 나는 다음 해결책을 정말로 빠르게 생각했지만 아마도 더 좋은 것이있을 것이다. jsfiddle.net/0o75bw43/1
Cruclax

1
@Cruclax는 입력 배열에서 모든 항목이 0 일 때 1을 모두 표시합니다
tony.0919

159

원래 십진 데이터에 의존하는 것에 대해 걱정하지 않는다면이 작업을 수행하는 많은 방법이 있습니다.

가장 많이 사용되는 첫 번째 방법은 가장 큰 나머지 방법입니다.

기본적으로

  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

또는 정수 값 대신 소수점 이하 하나를 표시하도록 선택할 수 있습니다. 따라서 숫자는 48.3과 23.9 등이 될 것입니다. 이것은 분산을 100에서 많이 떨어 뜨립니다.


5
American Mathematical Society – Apportionment II : Apportionment Systems 의 웹 사이트에있는이 "Feature Column" 은 몇 가지 유사한 '배분'방법을 설명합니다.
Kenny Evitt

1
이것은 거의 내 대답을 복사하여 붙여 넣은 것 같습니다 . stackoverflow.com/questions/5227215/… .
sawa

@ DStanley의 답변에 대한 귀하의 의견과 달리 9.596008 %는 9 %로 반올림되어 0.5 % 이상의 차이가 있습니다. 그래도 여전히 좋은 대답입니다.
Rolazaro Azeveires

33

아마도 이것을하기위한 "최상의"방법 ( "최상의"가 주관적인 용어이기 때문에 인용 된)은 당신이있는 곳의 (통합적이지 않은) 탈리를 유지하고 값을 반올림 하는 것 입니다.

그런 다음 기록과 함께 사용하여 어떤 값을 사용해야하는지 알아 내십시오. 예를 들어, 제공 한 값을 사용하십시오.

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임을 알 수 있습니다.

위의 세 번째 값에서이 값과 각 값의 반올림 차이를 확인할 수 있습니다. 9.596008일반적으로 반올림 되지만 10누적 된 71.211976올림은 올림됩니다 . 이는 이전 기준선에 추가하기 만하면 71됨을 의미합니다 .962


이것은 또한 대략 3 개의 값 과 같이 "문제적인"순서로 작동하며 그중 하나 는 반올림해야합니다.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
두 번째 방법은이 두 가지 문제를 모두 해결합니다. 첫 번째는 26, 25, 26, 23두 번째 1, 0, 1, 0, 1, 0, ...입니다.
paxdiablo

이 접근법은 또한 음수가 출력에
영향을주지

19

반올림의 목표는 가장 적은 양의 오류를 생성하는 것입니다. 단일 값을 반올림하는 경우 해당 프로세스는 간단하고 간단하며 대부분의 사람들이 쉽게 이해할 수 있습니다. 동시에 여러 숫자를 반올림하면 프로세스가 까다로워집니다. 오류가 어떻게 결합되는지, 즉 최소화해야하는 것을 정의해야합니다.

Varun Vohra잘 알려진 답변 은 절대 오류의 합계를 최소화하며 구현이 매우 간단합니다. 그러나 처리하지 않는 경우가 있습니다. 반올림의 결과는 24.25, 23.25, 27.25, 25.25무엇입니까? 그중 하나는 내림 대신 반올림해야합니다. 목록에서 첫 번째 또는 마지막 항목을 임의로 선택합니다.

아마도 절대 오류 대신 상대 오류 를 사용하는 것이 좋습니다 . 23.25를 24로 올림하면 3.2 %, 27.25를 28로 올림하면 2.8 % 만 바꿉니다. 이제 확실한 승자가 있습니다.

이것을 더 조정할 수 있습니다. 한 가지 일반적인 기술은 각 오류 를 제곱 하여 큰 오류가 작은 오류보다 불균형 적으로 더 많이 계산되도록하는 것입니다. 또한 비선형 제수를 사용하여 상대 오차를 얻으십시오 .1 %의 오차가 99 %의 오차보다 99 배 더 중요하다는 것은 옳지 않은 것 같습니다. 아래 코드에서 나는 제곱근을 사용했습니다.

완전한 알고리즘은 다음과 같습니다.

  1. 모두 반올림 한 후 백분율을 합산하고 100에서 뺍니다. 이는 몇 퍼센트를 반올림해야하는지 알려줍니다.
  2. 반올림 할 때와 반올림 할 때마다 각각의 백분율에 대해 두 개의 오류 점수를 생성합니다. 둘의 차이점을 생각해보십시오.
  3. 위에서 생성 된 오차 차이를 정렬하십시오.
  4. 반올림해야하는 백분율 수는 정렬 된 목록에서 항목을 가져오고 반올림 된 백분율을 1 씩 증가시킵니다.

예를 들어 같은 오류 합계를 가진 조합이 둘 이상있을 수 있습니다 33.3333333, 33.3333333, 33.3333333. 이것은 피할 수 없으며 결과는 완전히 임의적입니다. 아래 코드는 왼쪽의 값을 반올림하는 것을 선호합니다.

파이썬에서 모든 것을 합치는 것은 다음과 같습니다.

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
모든 조합을 고려할 필요는 없다고 생각합니다. 가중 오차가 반올림에서 0 으로 반올림하여 무한대 로 감소하는 순서로 진행 합니다 ( Verun Vohrasyonilevy의 '동일한'답변에 계량 을 도입하는 것).
greybeard

@ greybeard 당신이 맞아, 나는 이것을 너무 생각하고 있었다. 각 값마다 두 가지 오류 가 있기 때문에 오류를 정렬 할 수 없었지만 차이를 취하면 문제가 해결되었습니다. 답변을 업데이트했습니다.
Mark Ransom

실제 숫자가 0 % 일 때는 항상 0 %를 선호합니다. 따라서 추가하는 if actual == 0: return 0것이 error_gen좋습니다.
Nikolay Baluk

1
isclose시작 부분의 방법 은 무엇 round_to_100입니까?
toto_tico


7

반올림 숫자를 합산하지 마십시오. 부정확 한 결과가 나타납니다. 항의 수와 분수 부분의 분포에 따라 총계가 크게 떨어질 수 있습니다.

반올림 된 숫자를 표시 하지만 실제 값을 합산 하십시오. 숫자를 표현하는 방법에 따라 실제 방법은 다를 수 있습니다. 그렇게하면

 14
 48
 10
 29
 __
100

어떤 방식 으로든 불일치가 발생합니다. 하나의 값을 잘못된 방식으로 "반올림"하지 않고 100을 더하는 숫자를 표시하는 방법은 없습니다 (최소 오류는 9.596에서 9로 변경됩니다)

편집하다

다음 중 하나를 선택해야합니다.

  1. 품목의 정확도
  2. 합계의 정확도 (반올림 된 값을 합한 경우)
  3. 반올림 항목과 반올림 합계 간의 일관성)

백분율 # 3을 다룰 때 대부분의 경우 가장 좋은 방법은 개별 항목이 총 100 개가 아닌 경우보다 총계가 101 % 일 때 더 명확하고 개별 항목을 정확하게 유지하기 때문입니다. 내 의견으로는 "라운딩"9.596 ~ 9가 정확하지 않습니다.

이것을 설명하기 위해 때로는 개별 값이 반올림되고 총 100 %가 아닐 수 있음을 설명하는 각주를 추가합니다. 반올림을 이해하는 사람은 해당 설명을 이해할 수 있어야합니다.


6
인쇄 된 값이 최대 100 개가 아니기 때문에 그다지 도움이되지 않습니다. 질문의 목적은 사용자가 값이 잘못되었다고 생각하지 않도록하는 것이 었습니다.이 경우 대부분의 사람들은 전체를보고 비교할 때 .
vvohra87

@VarunVohra는 내 편집 내용을 읽었습니다. 숫자를 0.5 이상으로 반올림하지 않고 100을 더할 수는 없습니다.
D Stanley

1
@DStanley는 실제로 모든 숫자가 0.5보다 부끄러운 세트를 제외하고 할 수 있습니다. 내 대답을 확인하십시오-LRM은 정확히 그렇게합니다.
vvohra87

3
@VarunVohra 원래 예제에서 LRM은 14, 48, 9 및 29를 9.596에서 9로 "올림"합니다. 정수를 기준으로 할당하는 경우 LRM이 가장 정확하지만 여전히 하나의 결과를 더 많이 변경합니다. 반 단위보다.
D Stanley

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

좋은! 저에게 기초를 줬습니다. 열거 형은 내가 믿지만 ForEach가 없습니다
Jack0fshad0ws

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로 시작하십시오. "carry"를 첫 번째 값에 추가하고 자릅니다. 자르면서 잃어버린 양을 "캐리"에 넣습니다. 끝까지 내려 가면 반올림 된 숫자가 매번 원하는 합계에 합산됩니다.
Jeff Grigg 2

Carolyn Kay는 Access VB 2007에서이 구현을 제안했습니다. <code> ' "나머지 캐리"방법을 사용한 라운드 환불 달러 ref1 = rsQry! [환불 지불 $$$] * rsQry! [Property Value] / propValTot ref2 = ref1 + ref5 '반송 된 나머지를 0으로 시작하여 ref3 = ref2 * 100'100을 정수로 곱하십시오 ref4 = ref3 / 100 '100을 10 진수로 나누기 rsTbl! [환불 유료 $$$] = ref4' ref5 = ref2-ref4 '남은 잔고'반올림 숫자 '새 잔고를 나르십시오 </ code>
Jeff Grigg

2

한때 목표에 맞도록 일련의 숫자에 대한 최소한의 동요를 찾기 위해 둥근 도구를 썼습니다. 그것은 다른 문제 였지만 이론 상으로는 비슷한 아이디어를 사용할 수 있습니다. 이 경우 선택 사항이 있습니다.

따라서 첫 번째 요소의 경우 14로 올림하거나 13으로 내릴 수 있습니다. 반올림에 필요한 비용은 이진 정수 프로그래밍 의미에서 반올림보다 적습니다. 그 값을 더 먼 거리로 옮기십시오. 마찬가지로 각 숫자를 반올림 또는 내림 할 수 있으므로 선택해야 할 총 16 가지가 있습니다.

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

필자는 일반적으로 바이너리 정수 프로그래밍 도구 인 bintprog를 사용하여 MATLAB의 일반적인 문제를 해결할 것이지만 테스트 할 몇 가지 선택 사항 만 있으므로 간단한 루프로 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의 답변 참조). 증명은 간단합니다. 오류를 최소화하지 않는다고 가정하십시오. 그런 다음 반올림해야하는 일부 값 세트가 있어야하며 그 반대도 마찬가지입니다 (두 세트의 크기는 동일 함). 그러나 반올림 한 모든 값은 반올림 한 값 (및 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] 대신 [1, 33, 33, 33]을 반환합니다
yonilevy

@yonilevy 감사합니다. 지금 수정했습니다.
브루노

아직, [16.666, 16.666, 16.666, 16.666, 16.666, 16.666]의 경우 [16, 16, 17, 17, 17, 17] 대신 [15, 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 개의 숫자를 깎고 나머지는 바닥에 놓습니다.

(편집에 죄송합니다)


1
그것은 100에 더하는 숫자를 제공 할 수 있지만, 결국 3.9를 3으로, 25.1을 26으로
바꿀 수 있습니다

아니. 3.9는 4이고 25.1은 25입니다. 나는 가장 높은 값이 아닌 가장 높은 분수로 3 개의 숫자를 멈 춥니 다.
arunlalam

2
.9로 끝나는 분수가 너무 많으면 9 개의 값이 9.9 %이고 하나의 값이 10.9 인 경우 하나의 값은 9 %, 8은 10 %, 하나는 11 %입니다.
arunlalam

1

실제로 반올림 해야하는 경우 여기에 이미 매우 좋은 제안이 있습니다 (가장 큰 나머지, 최소 상대 오차 등).

또한 반올림하지 말아야 할 좋은 이유가 하나 있습니다 ( "더보기에는 좋지만"잘못된 "최소한 하나의 숫자가 표시됨).이를 해결하는 방법 (독자에게 경고)과 그것이 제가하는 일입니다.

"잘못된"숫자 부분을 추가하겠습니다.

세 가지 이벤트 / 엔티티 / ...가 있고 다음과 같은 비율이 있다고 가정합니다.

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 % 감소했습니다.

이는 정 성적 오류 일 수 있으며 초기 정량적 오류보다 상당히 나쁩니다.

하나는 전체 세트에 대한 근사치를 만들 수 있지만 첫 번째 날에 데이터를 게시해야 할 수도 있으므로 두 번째 날에 대해서는 알 수 없습니다. 따라서, 당신이 정말로, 근사치가 아니면, 아마 더 나은 것은 아닐 것입니다.


더 나은 테이블을 만드는 방법을 아는 사람은 편집 또는 방법 / 장소를 알려주십시오.
Rolazaro Azeveires

0

이것이 유효한지 아닌지 테스트 케이스까지이 작업을 수행 할 수 있습니다.

숫자가 k라고 가정 해 봅시다.

  1. 내림차순으로 백분율을 정렬합니다.
  2. 내림차순에서 각 백분율을 반복합니다.
  3. 첫 번째 백분율에 대한 k의 백분율을 계산하면 Math.Ceil이 출력됩니다.
  4. 다음 k = k-1
  5. 모든 백분율이 소비 될 때까지 반복하십시오.

0

나는 여기에 목록과 dicts에 대한 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

당신이 필요합니다 math, itertools, operator.


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 Stanley

@DStanley 동의합니다. 나는 달리 말하지 않았다. 나는 그 목적을 진술했다 . 매우 신중하게.
Lorne의 후작

2
충분히 공정하다-나는 당신이 말하려는 것을 잘못 해석했습니다. 두 경우 모두 은행 반올림을 사용하면 예제의 결과가 변경되지 않으므로 문제가 해결되지 않는다고 생각합니다.
D Stanley
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.