วิธีการทำให้เปอร์เซ็นต์การปัดเศษเพิ่มขึ้นถึง 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
มันเป็นสถานการณ์ที่พบบ่อยมากในการรายงาน - วิธีการแสดง "รวม" ของค่าทศนิยมที่ไม่ตรงกับผลรวมของค่าที่แสดง
D Stanley

1
ผลลัพธ์ "ขวา" ในกรณีตัวอย่างของคุณคืออะไร นั่นอาจแก้ปัญหาความขัดแย้งในสิ่งที่ "ดีที่สุด" คือทางออก
D Stanley

คำตอบ:


35

เนื่องจากไม่มีคำตอบที่นี่ดูเหมือนจะแก้ไขได้อย่างถูกต้องนี่เป็นเวอร์ชั่นกึ่งสับสนของฉันโดยใช้ขีดล่าง :

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
ถูกต้องฉันถ้าฉันผิด แต่นี่ไม่ใช่การดำเนินการตามขั้นตอนวิธีที่เสนอโดยคำตอบของฉัน (ไม่ควรล้างเครื่องหมายขีดล่าง)
vvohra87

@VarunVohra ขอโทษฉันไม่ได้สังเกตสิ่งนี้จนกระทั่งตอนนี้ใช่ดูเหมือนว่าอัลกอริทึมของคุณเหมือนกัน :) ไม่แน่ใจว่าทำไมโพสต์ของฉันเป็นคำตอบที่ยอมรับรหัส obfuscated เป็นเพียง 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 มันจะแสดงทั้งหมด 1 เมื่อรายการทั้งหมดเป็นศูนย์ในอาร์เรย์อินพุต
tony.0919

159

มีหลายวิธีในการทำเช่นนี้หากคุณไม่กังวลเกี่ยวกับการพึ่งพาข้อมูลทศนิยมต้นฉบับ

วิธีแรกและที่นิยมมากที่สุดคือวิธีที่ใหญ่ที่สุดเหลือ

ซึ่งโดยทั่วไป:

  1. ปัดเศษทุกอย่างลง
  2. รับความแตกต่างในผลรวมและ 100
  3. กระจายความแตกต่างโดยการเพิ่ม 1 ให้กับรายการในลำดับที่ลดลงของส่วนทศนิยมของพวกเขา

ในกรณีของคุณมันจะเป็นดังนี้:

13.626332%
47.989636%
 9.596008%
28.788024%

ถ้าคุณเอาส่วนจำนวนเต็มคุณจะได้

13
47
 9
28

ซึ่งเพิ่มได้มากถึง 97 และคุณต้องการเพิ่มอีกสามรายการ ทีนี้คุณดูที่ส่วนทศนิยมซึ่งก็คือ

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

และนำสิ่งที่มีค่ามากที่สุดไปจนถึงยอดรวม 100 คุณจะได้รับ:

14
48
 9
29

หรือคุณสามารถเลือกที่จะแสดงทศนิยมหนึ่งตำแหน่งแทนค่าจำนวนเต็ม ตัวเลขจะเป็น 48.3 และ 23.9 เป็นต้นซึ่งจะลดความแปรปรวนจาก 100 ลงมาก


5
"คอลัมน์คุณสมบัติ" นี้บนเว็บไซต์ของ American Mathematical Society - Apportionment II: Apportionment Systems - อธิบายวิธีการ 'การจัดสรร' ที่คล้ายคลึงกันหลายวิธี
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


สิ่งนี้ใช้ได้กับลำดับ "ปัญหา" เช่นค่าประมาณสามค่าซึ่งหนึ่งในนั้นควรปัดเศษขึ้น: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

วิธีการนี้ยังใช้งานได้ดีในการปัดเศษจำนวนเล็กน้อยเนื่องจากป้องกันการลบจำนวนลบบาป
Jonty5817

19

เป้าหมายของการปัดเศษคือการสร้างข้อผิดพลาดให้น้อยที่สุด เมื่อคุณปัดเศษค่าเดียวกระบวนการนั้นง่ายและตรงไปตรงมาและคนส่วนใหญ่เข้าใจได้ง่าย เมื่อคุณปัดเศษตัวเลขหลายตัวพร้อมกันกระบวนการจะซับซ้อนขึ้น - คุณต้องกำหนดว่าข้อผิดพลาดจะรวมกันอย่างไรเช่นจะต้องลดขนาดให้เล็กลง

คำตอบที่ดีได้รับการโหวตจากวรุณ 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คุณยังอาจมีมากกว่าหนึ่งร่วมกับผลรวมข้อผิดพลาดเดียวกันตัวอย่างเช่น สิ่งนี้หลีกเลี่ยงไม่ได้และผลลัพธ์จะเป็นไปตามอำเภอใจโดยสมบูรณ์ รหัสที่ฉันให้ด้านล่างชอบที่จะปัดเศษค่าทางด้านซ้าย

การรวมมันเข้าด้วยกันใน 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
ผมไม่คิดว่าคุณจะต้องพิจารณารวมกันทั้งหมด: ขั้นตอนในการสั่งซื้อลดลงลดลงในข้อผิดพลาดถ่วงน้ำหนักไปจากรอบให้เป็นศูนย์การรอบที่อินฟินิตี้ (สวยมากเพียงแค่การแนะนำการชั่งน้ำหนักเข้าVerun Vohras ของและของ yonilevy ( "เหมือนกัน") คำตอบ)
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 เป็นตัวเลือกที่ดีที่สุดเพราะจะเห็นได้ชัดเจนยิ่งขึ้นเมื่อผลรวมมีค่าเท่ากับ 101% มากกว่าเมื่อรายการแต่ละรายการไม่รวม 100 และคุณรักษาแต่ละรายการให้ถูกต้อง "การปัดเศษ" 9.596 ถึง 9 ไม่ถูกต้องในความคิดของฉัน

ในการอธิบายเรื่องนี้บางครั้งฉันเพิ่มเชิงอรรถที่อธิบายว่าค่าแต่ละค่าถูกปัดเศษและอาจไม่รวม 100% - ทุกคนที่เข้าใจการปัดเศษควรเข้าใจคำอธิบายนั้น


6
ไม่เป็นประโยชน์มากนักเนื่องจากค่าที่พิมพ์ออกมาจะไม่เพิ่มขึ้นถึง 100 จุดประสงค์ของคำถามคือเพื่อป้องกันไม่ให้ผู้ใช้คิดว่าค่าไม่ถูกต้องซึ่งในกรณีนี้คนส่วนใหญ่จะทำเมื่อดูและเปรียบเทียบกับผลรวม .
vvohra87

@VarunVohra อ่านการแก้ไขของฉันคุณไม่สามารถแสดงตัวเลขของคุณโดยที่พวกเขาเพิ่มได้ถึง 100 โดยไม่ต้อง "ปัดเศษ" มากกว่า 0.5
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/2 ของหนึ่งเซ็นต์ใน "พกพา" เพิ่ม "ดำเนินการ" เป็นค่าแรกและตัดทอน ทีนี้จำนวนเงินที่คุณสูญเสียจากการตัดทอนให้ใส่ไว้ใน "พกพา" ทำเช่นนี้จนสุดและตัวเลขที่ปัดเศษจะรวมกันเป็นผลรวมที่ต้องการทุกครั้ง
Jeff Grigg

Carolyn Kay แนะนำการดำเนินการนี้ใน Access VB 2007: <code> 'ดอลลาร์คืนเงินรอบโดยใช้วิธีการ "ดำเนินการส่วนที่เหลือ" ref1 = rsQry! [จ่ายเงินคืน $$$] * rsQry! [มูลค่าทรัพย์สิน] / propValTot ref2 = ref1 + ref5 'เพิ่มส่วนที่เหลือที่ดำเนินการศูนย์เพื่อเริ่ม ref3 = ref2 * 100' คูณด้วย 100 เป็นจำนวนเต็ม ref4 = ref3 / 100 'หารด้วย 100 ลงในจำนวนทศนิยม rsTbl! [Refund Paid $$$] = ref4' ใส่ " ส่วนที่เหลือ "หมายเลขที่ถูกปัดเศษในตาราง ref5 = ref2 - ref4 'ดำเนินการส่วนที่เหลือใหม่ </code>
Jeff Grigg

2

ฉันเคยเขียนเครื่องมือ unround เพื่อค้นหาความยุ่งเหยิงน้อยที่สุดให้กับชุดของตัวเลขเพื่อให้ตรงกับเป้าหมาย มันเป็นปัญหาที่แตกต่าง แต่ในทางทฤษฎีสามารถใช้ความคิดที่คล้ายกันที่นี่ ในกรณีนี้เรามีตัวเลือกมากมาย

ดังนั้นสำหรับองค์ประกอบแรกเราสามารถปัดเศษมันได้มากถึง 14 หรือลดลงเหลือ 13 ค่าใช้จ่าย (ในความรู้สึกการเขียนโปรแกรมเลขฐานสองไบนารี) ของการทำเช่นนั้นจะน้อยกว่าสำหรับรอบขึ้นกว่ารอบลงเพราะเราต้องการ ย้ายค่านั้นเป็นระยะทางที่มากขึ้น ในทำนองเดียวกันเราสามารถปัดเศษตัวเลขแต่ละตัวขึ้นหรือลงดังนั้นจึงมีทั้งหมด 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) การพิสูจน์นั้นง่าย: สมมติว่ามันไม่ลดข้อผิดพลาดให้น้อยที่สุด จากนั้นจะต้องมีชุดของค่าที่ปัดเศษลงซึ่งควรปัดเศษขึ้นและในทางกลับกัน (ทั้งสองชุดมีขนาดเท่ากัน) แต่ทุกค่าที่ปัดเศษลงจะเป็นจำนวนเต็มถัดไปมากกว่าค่าใด ๆ ที่ปัดเศษขึ้น (และ 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] มันส่งคืน [1, 33, 33, 33] แทนที่จะเป็นความแม่นยำมากขึ้น [34, 33, 33, 0]
yonilevy

@ yonilevy ขอบคุณสำหรับสิ่งนั้น แก้ไขแล้ว
บรูโน่

ยังสำหรับ [16.666, 16.666, 16.666, 16.666, 16.666, 16.666] มันส่งคืน [15, 17, 17, 17, 17, 17, 17] มากกว่า [16, 16, 17, 17, 17, 17] คำตอบ
yonilevy

2

ฉันไม่แน่ใจว่าคุณต้องการความแม่นยำระดับใด แต่สิ่งที่ฉันจะทำคือเพียงแค่เพิ่ม 1 nหมายเลขแรกnเป็นเพดานของผลรวมทั้งหมดของทศนิยม ในกรณีนี้คือ3ดังนั้นฉันจะเพิ่ม 1 ถึง 3 รายการแรกและเพิ่มส่วนที่เหลือ แน่นอนว่ามันไม่ถูกต้องอย่างยิ่งบางตัวเลขอาจถูกปัดขึ้นหรือลงเมื่อมันไม่ควร แต่มันก็ใช้ได้และจะส่งผลให้ 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
RobG

ไม่ 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 นั้นใกล้กับ 33 มากกว่าถึง 34

แต่ตอนนี้คุณมีข้อผิดพลาดที่ใหญ่กว่า เมื่อเปรียบเทียบกับวันที่ 2 ถึงวันที่ 1 ค่าเปอร์เซ็นต์ที่แท้จริงของ A จะเพิ่มขึ้น 0.01% แต่การประมาณจะลดลง 1%

นั่นคือข้อผิดพลาดเชิงคุณภาพอาจจะค่อนข้างแย่กว่าข้อผิดพลาดเชิงปริมาณเริ่มต้น

หนึ่งสามารถคิดประมาณทั้งชุด แต่คุณอาจต้องเผยแพร่ข้อมูลในวันที่หนึ่งดังนั้นคุณจะไม่ทราบเกี่ยวกับวันที่สอง ดังนั้นถ้าคุณจริง ๆ ต้องประมาณคุณอาจจะไม่ดีกว่า


ใครก็ตามที่รู้วิธีสร้างตารางที่ดีขึ้นโปรดแก้ไขหรือบอกวิธี / สถานที่
Rolazaro Azeveires

0

ตรวจสอบว่ามันถูกต้องหรือไม่เท่ากรณีทดสอบของฉันฉันสามารถทำงานได้

สมมุติว่า number คือ k

  1. เปอร์เซ็นต์การจัดเรียงโดยมากไปหาน้อย
  2. วนซ้ำแต่ละเปอร์เซ็นต์จากลำดับถัดลงมา
  3. คำนวณเปอร์เซ็นต์ของ k สำหรับเปอร์เซ็นต์แรกใช้ Math.Ceil ของเอาต์พุต
  4. ถัดไป k = k-1
  5. วนซ้ำจนกว่าเปอร์เซ็นต์ทั้งหมดจะถูกใช้

0

ฉันใช้วิธีจากคำตอบของ Varun Vohra ที่นี่สำหรับทั้งรายการและ dicts

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

นี่คือการใช้ Python ที่ง่ายขึ้นของคำตอบ @ 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

คุณต้องmath, ,itertoolsoperator


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

นี่เป็นกรณีของการปัดเศษของนายธนาคารหรือที่เรียกว่า 'round half-even' รองรับ BigDecimal โดยมีวัตถุประสงค์คือเพื่อให้แน่ใจว่าการปัดเศษยอดคงเหลือออกมานั้นไม่ได้เป็นที่ชื่นชอบของลูกค้าธนาคาร


5
ไม่แน่ใจว่าการปัดเศษยอดคงเหลือ - เพียงแค่ลดจำนวนข้อผิดพลาดโดยกระจายครึ่งรอบระหว่างเลขคู่และเลขคี่ ยังคงมีสถานการณ์ที่การปัดเศษของธนาคารก่อให้เกิดผลลัพธ์ที่ไม่ถูกต้อง
D Stanley

@DStanley เห็นด้วย ฉันไม่ได้พูดอย่างอื่น ผมกล่าวของวัตถุประสงค์ อย่างระมัดระวัง.
มาร์ควิสแห่ง Lorne

2
ยุติธรรมเพียงพอ - ฉันตีความสิ่งที่คุณพยายามจะพูดผิด ไม่ว่าในกรณีใดฉันไม่คิดว่าจะแก้ปัญหาเนื่องจากการปัดเศษของนายธนาคารจะไม่เปลี่ยนผลลัพธ์ในตัวอย่าง
D Stanley
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.