Làm thế nào để làm cho tỷ lệ phần trăm tròn thêm lên đến 100%


192

Hãy xem xét bốn phần trăm dưới đây, được biểu thị dưới dạng floatsố:

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

Tôi cần phải biểu diễn các tỷ lệ phần trăm này dưới dạng số nguyên. Nếu tôi chỉ đơn giản sử dụng Math.round(), tôi kết thúc với tổng số 101%.

14 + 48 + 10 + 29 = 101

Nếu tôi sử dụng parseInt(), tôi kết thúc với tổng số 97%.

13 + 47 + 9 + 28 = 97

Thuật toán tốt để biểu thị bất kỳ số phần trăm nào dưới dạng số nguyên trong khi vẫn duy trì tổng số 100%?


Chỉnh sửa : Sau khi đọc một số ý kiến ​​và câu trả lời, rõ ràng có nhiều cách để giải quyết vấn đề này.

Trong suy nghĩ của tôi, để giữ đúng với các con số, kết quả "đúng" là kết quả giảm thiểu lỗi tổng thể, được xác định bằng cách làm tròn số lỗi sẽ giới thiệu so với giá trị thực tế:

        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)

Trong trường hợp hòa (3,33, 3,33, 3,33), một quyết định tùy ý có thể được đưa ra (ví dụ 3, 4, 3).


21
Giả sử bạn có 3,33, 3,33 và 3,33. Cái nào bạn sẽ làm 4?
RobG

3
Chính xác. Câu hỏi thể hiện sự mâu thuẫn trong các điều khoản.
Hầu tước Lorne

4
Đây là một tình huống rất phổ biến trong báo cáo - cách hiển thị "tổng" các giá trị thập phân không luôn khớp với tổng các giá trị được hiển thị.
D Stanley

1
Kết quả "đúng" trong trường hợp ví dụ của bạn là gì? Điều đó có thể giải quyết những bất đồng về giải pháp "tốt nhất" là gì.
D Stanley

Câu trả lời:


35

Vì không có câu trả lời nào ở đây có vẻ giải quyết đúng đắn, đây là phiên bản nửa kín của tôi bằng cách sử dụng dấu gạch dưới :

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
Chỉnh sửa cho tôi nếu tôi sai, nhưng đây không phải là triển khai Thuật toán được đề xuất bởi câu trả lời của tôi phải không? (Không xóa trên dấu gạch dưới)
vvohra87

@VarunVohra xin lỗi tôi đã không nhận thấy điều này cho đến bây giờ, vâng, có vẻ như thuật toán của bạn giống nhau :) không chắc tại sao bài đăng của tôi là câu trả lời được chấp nhận, mã bị
che giấu

@yonilevy Đã xóa bình luận của tôi; Tôi chỉ không nhận ra rằng nó đáng lẽ phải trả về một danh sách đã được sắp xếp. Tôi xin lỗi!
Zack Burt

2
Có dấu hiệu với chức năng này khi phần tử cuối cùng là 0 và phần tử trước thêm vào 100. Vd Cái cuối cùng trả về logic -1. Tôi đã nghĩ đến giải pháp sau đây thực sự nhanh chóng nhưng có lẽ có một thứ tốt hơn: jsfiddle.net/0o75bw43/1
Cruclax

1
@Cruclax nó hiển thị tất cả 1 khi tất cả các mục nhập bằng 0 trong mảng đầu vào
tony.0919

158

Có nhiều cách để làm điều này, miễn là bạn không quan tâm đến việc phụ thuộc vào dữ liệu thập phân ban đầu.

Phương pháp đầu tiên và có lẽ phổ biến nhất sẽ là Phương pháp còn lại lớn nhất

Về cơ bản là:

  1. Làm tròn mọi thứ xuống
  2. Lấy chênh lệch về tổng và 100
  3. Phân phối sự khác biệt bằng cách thêm 1 vào các mục theo thứ tự giảm dần các phần thập phân của chúng

Trong trường hợp của bạn, nó sẽ như thế này:

13.626332%
47.989636%
 9.596008%
28.788024%

Nếu bạn lấy phần nguyên, bạn nhận được

13
47
 9
28

có thêm tới 97 và bạn muốn thêm ba mục nữa. Bây giờ, bạn nhìn vào các phần thập phân, đó là

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

và lấy những cái lớn nhất cho đến khi tổng số đạt 100. Vì vậy, bạn sẽ nhận được:

14
48
 9
29

Ngoài ra, bạn có thể chỉ cần chọn hiển thị một vị trí thập phân thay vì giá trị số nguyên. Vì vậy, các con số sẽ là 48,3 và 23,9, v.v ... Điều này sẽ làm giảm phương sai từ 100 rất nhiều.


5
"Cột tính năng" này trên trang web của Hiệp hội toán học Hoa Kỳ - Apportionment II: Apportionment Systems - mô tả một số phương pháp 'phân bổ' tương tự.
Kenny Evitt

1
Điều này gần giống như một bản sao và dán câu trả lời của tôi ở đây stackoverflow.com/questions/5227215/ .
sawa

Lưu ý rằng, trái với nhận xét của bạn về câu trả lời của @DStanley, trong câu trả lời của bạn, 9.596008% được làm tròn thành 9%, chênh lệch nhiều hơn 0,5%. Vẫn là một câu trả lời tốt.
Rolazaro Azeveires

32

Có lẽ cách "tốt nhất" để làm điều này (được trích dẫn vì "tốt nhất" là một thuật ngữ chủ quan) là giữ một kiểm đếm (không tách rời) về vị trí của bạn và làm tròn giá trị đó .

Sau đó sử dụng cùng với lịch sử để tìm ra giá trị nào nên được sử dụng. Ví dụ: sử dụng các giá trị bạn đã cung cấp:

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

Ở mỗi giai đoạn, bạn không làm tròn số. Thay vào đó, bạn làm tròn giá trị tích lũy và tìm ra số nguyên tốt nhất đạt được giá trị đó từ đường cơ sở trước - đường cơ sở đó là giá trị tích lũy (làm tròn) của hàng trước đó.

Điều này hoạt động vì bạn không bị mất thông tin ở từng giai đoạn mà thay vào đó sử dụng thông tin một cách thông minh hơn. Các giá trị làm tròn 'chính xác' nằm trong cột cuối cùng và bạn có thể thấy rằng chúng có tổng bằng 100.

Bạn có thể thấy sự khác biệt giữa giá trị này và làm tròn một cách mù quáng từng giá trị, trong giá trị thứ ba ở trên. Trong khi 9.596008thông thường sẽ làm tròn lên 10, tích lũy 71.211976chính xác làm tròn xuống 71- điều này có nghĩa là chỉ 9cần thiết để thêm vào đường cơ sở trước đó 62.


Điều này cũng hoạt động cho chuỗi "có vấn đề" như ba giá trị đại khái , trong đó một trong số chúng nên được làm tròn: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
Cách tiếp cận thứ hai khắc phục cả những vấn đề đó. Cái thứ nhất cho 26, 25, 26, 23, cái thứ hai 1, 0, 1, 0, 1, 0, ....
paxdiablo

Cách tiếp cận này cũng hoạt động tốt để làm tròn số nhỏ vì nó ngăn chặn số âm gây ra đầu ra
Jonty5817

18

Mục tiêu của làm tròn là tạo ra ít lỗi nhất. Khi bạn làm tròn một giá trị, quá trình đó đơn giản và dễ hiểu và hầu hết mọi người đều hiểu nó một cách dễ dàng. Khi bạn làm tròn nhiều số cùng một lúc, quy trình sẽ phức tạp hơn - bạn phải xác định cách các lỗi sẽ kết hợp, tức là những gì phải được giảm thiểu.

Các câu trả lời tốt bình chọn bởi Varun Vohra giảm thiểu tổng các lỗi tuyệt đối, và nó rất đơn giản để thực hiện. Tuy nhiên, có những trường hợp cạnh không xử lý - kết quả của làm tròn là 24.25, 23.25, 27.25, 25.25gì? Một trong những thứ đó cần được làm tròn lên thay vì xuống. Bạn có thể chỉ cần tùy ý chọn cái đầu tiên hoặc cuối cùng trong danh sách.

Có lẽ tốt hơn là sử dụng lỗi tương đối thay vì lỗi tuyệt đối . Làm tròn 23,25 lên đến 24 thay đổi 3,2% trong khi làm tròn 27,25 lên đến 28 chỉ thay đổi 2,8%. Bây giờ có một người chiến thắng rõ ràng.

Có thể điều chỉnh điều này hơn nữa. Một kỹ thuật phổ biến là hình vuông mỗi lỗi, do đó lỗi lớn đếm không tương xứng hơn những cái nhỏ. Tôi cũng sử dụng một ước số phi tuyến tính để nhận được lỗi tương đối - có vẻ không đúng khi lỗi ở mức 1% quan trọng hơn 99 lần so với lỗi ở mức 99%. Trong đoạn mã dưới đây tôi đã sử dụng căn bậc hai.

Thuật toán hoàn chỉnh như sau:

  1. Tính tổng tỷ lệ phần trăm sau khi làm tròn tất cả xuống và trừ đi 100. Điều này cho bạn biết có bao nhiêu phần trăm phải được làm tròn thay thế.
  2. Tạo hai điểm lỗi cho mỗi tỷ lệ phần trăm, một khi được làm tròn xuống và một khi được làm tròn lên. Lấy sự khác biệt giữa hai.
  3. Sắp xếp các khác biệt lỗi được tạo ra ở trên.
  4. Đối với số phần trăm cần làm tròn lên, hãy lấy một mục từ danh sách đã sắp xếp và tăng tỷ lệ phần trăm làm tròn xuống 1.

Ví dụ, bạn vẫn có thể có nhiều kết hợp có cùng một tổng lỗi 33.3333333, 33.3333333, 33.3333333. Điều này là không thể tránh khỏi, và kết quả sẽ hoàn toàn tùy ý. Mã tôi đưa ra dưới đây thích làm tròn các giá trị bên trái.

Đặt tất cả lại với nhau trong Python trông như thế này.

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]

Như bạn có thể thấy với ví dụ cuối cùng, thuật toán này vẫn có khả năng mang lại kết quả không trực quan. Mặc dù 89.0 không cần làm tròn bất cứ điều gì, một trong những giá trị trong danh sách đó cần được làm tròn; lỗi tương đối thấp nhất dẫn đến việc làm tròn giá trị lớn đó thay vì các lựa chọn thay thế nhỏ hơn nhiều.

Câu trả lời này ban đầu ủng hộ việc đi qua mọi sự kết hợp có thể của làm tròn lên / làm tròn xuống, nhưng như đã chỉ ra trong các ý kiến, một phương pháp đơn giản hơn sẽ hiệu quả hơn. Thuật toán và mã phản ánh sự đơn giản hóa đó.


1
Tôi không nghĩ rằng bạn cần phải xem xét tất cả các kết hợp: quá trình theo thứ tự giảm dần thả do lỗi trọng đi từ vòng tới zero đến vòng đến vô cùng (khá nhiều chỉ giới thiệu nặng vào của Verun Vohrascủa yonilevy ( "giống hệt") câu trả lời).
greybeard

@greybeard bạn nói đúng, tôi đã đánh giá cao điều này. Tôi không thể sắp xếp lỗi vì có hai lỗi cho mỗi giá trị, nhưng lấy sự khác biệt đã giải quyết vấn đề đó. Tôi đã cập nhật câu trả lời.
Đánh dấu tiền chuộc

Tôi thích luôn luôn có 0% khi con số thực tế là 0%. Vì vậy, thêm if actual == 0: return 0vào error_gencông trình tuyệt vời.
Nikolay Baluk

1
là những gì isclosephương pháp vào đầu round_to_100?
toto_tico


7

KHÔNG tổng các số làm tròn. Bạn sẽ có kết quả không chính xác. Tổng số có thể được giảm đáng kể tùy thuộc vào số lượng các điều khoản và phân phối các phần phân số.

Hiển thị các số làm tròn nhưng tổng các giá trị thực tế. Tùy thuộc vào cách bạn trình bày các con số, cách thực tế để làm điều đó sẽ khác nhau. Bằng cách đó bạn có được

 14
 48
 10
 29
 __
100

Bất cứ cách nào bạn đi bạn sẽ có sự khác biệt. Ví dụ của bạn không có cách nào để hiển thị các số cộng tới 100 mà không "làm tròn" một giá trị sai cách (ít nhất là lỗi sẽ thay đổi 9.596 thành 9)

BIÊN TẬP

Bạn cần chọn giữa một trong những điều sau đây:

  1. Độ chính xác của các mặt hàng
  2. Độ chính xác của tổng (nếu bạn tính tổng các giá trị làm tròn)
  3. Tính nhất quán giữa các mục được làm tròn và tổng số được làm tròn)

Hầu hết thời gian khi giao dịch với tỷ lệ phần trăm # 3 là lựa chọn tốt nhất vì rõ ràng hơn khi tổng số bằng 101% so với khi các mục riêng lẻ không tổng cộng đến 100 và bạn giữ chính xác các mục riêng lẻ. "Làm tròn" 9.596 đến 9 là không chính xác theo ý kiến ​​của tôi.

Để giải thích điều này đôi khi tôi thêm một chú thích giải thích rằng các giá trị riêng lẻ được làm tròn và có thể không hoàn toàn 100% - bất kỳ ai hiểu làm tròn đều có thể hiểu lời giải thích đó.


6
Điều đó không hữu ích lắm vì các giá trị được in sẽ không tăng thêm 100. Mục đích của câu hỏi là ngăn người dùng nghĩ rằng các giá trị không chính xác, trong trường hợp này, hầu hết mọi người sẽ làm khi nhìn và so sánh với tổng số .
vvohra87

@VarunVohra đọc bản chỉnh sửa của tôi, bạn KHÔNG THỂ hiển thị số của mình sao cho chúng thêm tới 100 mà không "làm tròn" một số nhiều hơn 0,5.
D Stanley

1
@DStanley thực sự, chặn một tập hợp trong đó tất cả các số là 0,5, bạn có thể. Kiểm tra câu trả lời của tôi - LRM thực hiện chính xác điều đó.
vvohra87

3
@VarunVohra Trong ví dụ ban đầu, LRM sẽ mang lại 14, 48, 9 và 29 sẽ "làm tròn" 9.596 thành 9. Nếu chúng ta phân bổ dựa trên toàn bộ số thì LRM sẽ chính xác nhất, nhưng nó vẫn thay đổi một kết quả nhiều hơn hơn một nửa đơn vị.
D Stanley

7

Tôi đã viết một trình trợ giúp làm tròn phiên bản C #, thuật toán giống như câu trả lời của Varun Vohra , hy vọng nó có ích.

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

Nó vượt qua bài kiểm tra Đơn vị sau:

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

Đẹp! đã cho tôi một cơ sở nền tảng để bắt đầu với .. Vô số không có ForEach mặc dù tôi tin
Jack0fshad0ws

4

Bạn có thể thử theo dõi lỗi của mình do làm tròn, và sau đó làm tròn với hạt nếu sai số tích lũy lớn hơn phần phân số của số hiện tại.

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

Không chắc chắn nếu điều này sẽ làm việc nói chung, nhưng nó dường như hoạt động tương tự nếu thứ tự được đảo ngược:

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

Tôi chắc chắn có những trường hợp cạnh mà điều này có thể bị hỏng, nhưng bất kỳ cách tiếp cận nào sẽ ít nhất là tùy ý vì về cơ bản bạn đang sửa đổi dữ liệu đầu vào của mình.


2
Kế toán và nhân viên ngân hàng đã sử dụng một kỹ thuật tương tự trong hàng trăm năm. "Mang phần còn lại" từ hàng này sang hàng tiếp theo. Bắt đầu với 1/2 của một xu trong "thực hiện". Thêm "thực hiện" vào giá trị đầu tiên và cắt ngắn. Bây giờ số tiền bạn đã mất bằng cách cắt bớt, hãy đặt số tiền đó vào "thực hiện". Làm điều này tất cả các cách xuống, và các số được làm tròn sẽ cộng vào tổng số mong muốn chính xác mỗi lần.
Jeff Grigg

Carolyn Kay đã đề xuất triển khai này trong Access VB 2007: <code> 'Làm tròn số tiền hoàn lại bằng cách sử dụng phương thức "mang phần còn lại" ref1 = rsQry! [Hoàn lại tiền $$$] * rsQry! [Giá trị tài sản] / propValTot ref2 = ref1 + ref5 'Thêm phần còn lại mang theo, số 0 để bắt đầu ref3 = ref2 * 100' Nhân với 100 thành số nguyên ref4 = ref3 / 100 'Chia cho 100 thành số thập phân rsTbl! [Hoàn lại tiền $$$] = ref4' Đặt " phần còn lại "số được làm tròn trong bảng ref5 = ref2 - ref4 'Mang phần còn lại mới </ code>
Jeff Grigg

2

Tôi đã từng viết một công cụ unround, để tìm ra nhiễu loạn tối thiểu cho một tập hợp số để khớp với mục tiêu. Đó là một vấn đề khác, nhưng về mặt lý thuyết, người ta có thể sử dụng một ý tưởng tương tự ở đây. Trong trường hợp này, chúng tôi có một bộ các lựa chọn.

Do đó, đối với phần tử đầu tiên, chúng ta có thể làm tròn nó lên đến 14 hoặc xuống đến 13. Chi phí (theo nghĩa lập trình số nguyên nhị phân) của việc làm như vậy ít hơn cho việc làm tròn lên so với làm tròn xuống, bởi vì làm tròn xuống đòi hỏi chúng ta di chuyển giá trị đó một khoảng cách lớn hơn. Tương tự, chúng ta có thể làm tròn mỗi số lên hoặc xuống, do đó, có tổng cộng 16 lựa chọn chúng ta phải chọn.

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

Tôi thường giải quyết vấn đề chung trong MATLAB, ở đây sử dụng bintprog, một công cụ lập trình số nguyên nhị phân, nhưng chỉ có một vài lựa chọn để kiểm tra, do đó, đủ dễ dàng với các vòng lặp đơn giản để kiểm tra từng trong số 16 lựa chọn thay thế. Ví dụ: giả sử chúng ta làm tròn bộ này là:

 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

Tổng số lỗi tuyệt đối được thực hiện là 1.25266. Nó có thể được giảm nhẹ bằng cách làm tròn thay thế sau đây:

 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

Trong thực tế, đây sẽ là giải pháp tối ưu về mặt lỗi tuyệt đối. Tất nhiên, nếu có 20 thuật ngữ, không gian tìm kiếm sẽ có kích thước 2 ^ 20 = 1048576. Trong 30 hoặc 40 thuật ngữ, không gian đó sẽ có kích thước đáng kể. Trong trường hợp đó, bạn sẽ cần sử dụng một công cụ có thể tìm kiếm không gian một cách hiệu quả, có thể sử dụng một sơ đồ nhánh và ràng buộc.


Chỉ để tham khảo trong tương lai: thuật toán "phần còn lại lớn nhất" phải giảm thiểu tổng lỗi tuyệt đối theo số liệu của bạn (Xem câu trả lời của @ varunvohra). Bằng chứng rất đơn giản: giả sử nó không giảm thiểu lỗi. Sau đó, phải có một số tập hợp các giá trị mà nó làm tròn sẽ được làm tròn lên và ngược lại (hai bộ có cùng kích thước). Nhưng mọi giá trị mà nó làm tròn xuống hơn từ số nguyên tiếp theo so với bất kỳ giá trị nào nó làm tròn (và vv) nên số lượng lỗi mới phải lớn hơn. QED. Tuy nhiên, nó không hoạt động cho tất cả các số liệu lỗi; các thuật toán khác là cần thiết.
rici

2

Tôi nghĩ rằng những điều sau đây sẽ đạt được những gì bạn đang có sau

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]

Một điều cuối cùng, tôi đã chạy hàm bằng cách sử dụng các số ban đầu trong câu hỏi để so sánh với đầu ra mong muốn

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

Điều này khác với những gì câu hỏi muốn => [48, 29, 14, 9]. Tôi không thể hiểu điều này cho đến khi tôi nhìn vào tổng số lỗi

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

Về cơ bản, kết quả từ chức năng của tôi thực sự đưa ra ít lỗi nhất.

Fiddle ở đây


đó là khá nhiều những gì tôi đã nghĩ, với sự khác biệt là lỗi nên được đo tương đối với giá trị (làm tròn 9,8 đến 10 là một lỗi lớn hơn so với làm tròn từ 19,8 đến 20). Điều này có thể được thực hiện dễ dàng bằng cách phản ánh nó trong cuộc gọi lại sắp xếp.
poezn

điều này sai với [33,33, 33,33, 33,33, 0,1], nó trả về [1, 33, 33, 33] thay vì chính xác hơn [34, 33, 33, 0]
yonilevy

@yonilevy Cảm ơn vì điều đó. Đã sửa bây giờ.
Bruno

chưa câu trả lời
yonilevy

2

Tôi không chắc bạn cần mức độ chính xác nào, nhưng những gì tôi sẽ làm chỉ đơn giản là thêm 1 nsố đầu tiên , nlà mức trần của tổng số thập phân. Trong trường hợp này 3, vì vậy tôi sẽ thêm 1 vào 3 mục đầu tiên và bỏ phần còn lại. Tất nhiên điều này không phải là siêu chính xác, một số con số có thể được làm tròn lên hoặc xuống khi không nên nhưng nó hoạt động ổn và sẽ luôn cho kết quả 100%.

Vì vậy [ 13.626332, 47.989636, 9.596008, 28.788024 ]sẽ là [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

Bạn luôn có thể thông báo cho người dùng rằng các số được làm tròn và có thể không chính xác ...


1

Nếu bạn làm tròn nó, không có cách nào tốt để có được nó giống hệt nhau trong mọi trường hợp.

Bạn có thể lấy phần thập phân của N phần trăm bạn có (trong ví dụ bạn đưa ra là 4).

Thêm phần thập phân. Trong ví dụ của bạn, bạn có tổng số phần phân số = 3.

Trần 3 số với phân số cao nhất và sàn còn lại.

(Xin lỗi vì đã chỉnh sửa)


1
Trong khi điều đó có thể cung cấp số cộng thêm 100, cuối cùng bạn có thể biến 3.9 thành 3 và 25.1 thành 26.
RobG

Không. 3.9 sẽ là 4 và 25.1 sẽ là 25. tôi đã nói trần 3 số có phân số cao nhất không phải là giá trị cao nhất.
arunlalam

2
nếu có quá nhiều phân số kết thúc bằng .9 nói 9 giá trị 9,9% và một giá trị 10,9 thì có một giá trị sẽ kết thúc là 9%, 8 là 10% và một là 11%.
arunlalam

1

Nếu bạn thực sự phải làm tròn chúng, đã có những gợi ý rất tốt ở đây (phần còn lại lớn nhất, lỗi tương đối ít nhất, v.v.).

Cũng có một lý do chính đáng để không làm tròn (bạn sẽ nhận được ít nhất một số "có vẻ tốt hơn" nhưng là "sai") và cách giải quyết điều đó (cảnh báo độc giả của bạn) và đó là những gì tôi làm.

Hãy để tôi thêm vào phần số "sai".

Giả sử bạn có ba sự kiện / thực thể / ... với một số tỷ lệ phần trăm mà bạn ước tính là:

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

Sau đó, các giá trị thay đổi một chút, để

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

Bảng đầu tiên có vấn đề đã được đề cập là có số "sai": 33,34 gần với 33 hơn 34.

Nhưng bây giờ bạn có một lỗi lớn hơn. So sánh ngày 2 với ngày 1, giá trị phần trăm thực của A tăng 0,01%, nhưng xấp xỉ cho thấy giảm 1%.

Đó là một lỗi định tính, có lẽ khá tệ hơn là lỗi định lượng ban đầu.

Người ta có thể đưa ra một xấp xỉ cho toàn bộ nhưng bạn có thể phải xuất bản dữ liệu vào ngày đầu tiên, do đó bạn sẽ không biết về ngày thứ hai. Vì vậy, trừ khi bạn thực sự, thực sự, phải gần đúng, bạn có thể tốt hơn không.


Bất cứ ai biết cách tạo ra các bảng tốt hơn xin vui lòng chỉnh sửa hoặc cho tôi biết làm thế nào / ở đâu
Rolazaro Azeveires

0

kiểm tra xem điều này có hợp lệ hay không xa như các trường hợp thử nghiệm của tôi, tôi có thể làm việc này được không.

giả sử số là k;

  1. sắp xếp tỷ lệ phần trăm bằng cách giảm dần oder.
  2. lặp đi lặp lại qua từng phần trăm từ thứ tự giảm dần.
  3. tính phần trăm của k cho phần trăm đầu tiên lấy Math.Ceil của đầu ra.
  4. k = k-1 tiếp theo
  5. lặp đi lặp lại cho đến khi tất cả phần trăm được tiêu thụ.

0

Tôi đã thực hiện phương pháp từ câu trả lời của Varun Vohra tại đây cho cả danh sách và tài liệu.

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

Đây là cách triển khai Python đơn giản hơn của câu trả lời @ 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

Bạn cần math, itertools, operator.


0

Đối với những người có tỷ lệ phần trăm trong Sê-ri gấu trúc, đây là ý nghĩa của tôi về phương pháp còn lại lớn nhất (như trong câu trả lời của Varun Vohra ), nơi bạn thậm chí có thể chọn số thập phân mà bạn muốn làm tròn.

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

Đây là trường hợp làm tròn số của ngân hàng, còn gọi là "làm tròn một nửa chẵn". Nó được hỗ trợ bởi BigDecimal. Mục đích của nó là để đảm bảo làm tròn số dư, nghĩa là không ủng hộ khách hàng của ngân hàng.


5
Nó KHÔNG đảm bảo làm tròn số dư ra - nó chỉ làm giảm số lượng lỗi bằng cách phân phối nửa vòng giữa số chẵn và số lẻ. Vẫn có những kịch bản mà các nhân viên ngân hàng làm tròn tạo ra kết quả không chính xác.
D Stanley

@DStanley Đồng ý. Tôi đã không nói khác. Tôi đã nêu mục đích của nó . Rất cẩn thận.
Hầu tước Lorne

2
Đủ công bằng - tôi hiểu sai những gì bạn đang cố nói. Trong cả hai trường hợp, tôi không nghĩ rằng nó giải quyết được vấn đề vì sử dụng làm tròn ngân hàng sẽ không thay đổi kết quả trong ví dụ.
D Stanley
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.