Yuvarlak yüzdeler nasıl yapılır% 100'e kadar


192

floatSayılarla gösterilen aşağıdaki dört yüzdeyi düşünün :

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

Bu yüzdeleri tam sayılar olarak göstermem gerekiyor. Sadece kullanırsam Math.round(), toplamda% 101 ile sonuçlanırım.

14 + 48 + 10 + 29 = 101

Eğer kullanırsam parseInt(), toplamda% 97 ile sonuçlanırım.

13 + 47 + 9 + 28 = 97

Toplam% 100'ü korurken, herhangi bir yüzdeyi tam sayı olarak göstermek için iyi bir algoritma nedir?


Düzenleme : Bazı yorum ve cevapları okuduktan sonra, açıkça bu sorunu çözmek için gitmek için birçok yol vardır.

Aklımda, sayılara sadık kalmak için "doğru" sonuç, gerçek değere göre ne kadar hata yuvarlaması getireceği ile tanımlanan genel hatayı en aza indiren sonuçtur:

        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)

Beraberlik durumunda (3.33, 3.33, 3.33) keyfi bir karar verilebilir (örn. 3, 4, 3).


21
Varsayalım ki 3.33, 3.33 ve 3.33. Hangisini yapacaksın 4?
RobG

3
Kesinlikle. Soru, bir çelişki içeriyor.
Lorne Marquis

4
Raporlamada çok yaygın bir senaryodur - her zaman görüntülenen değerlerin toplamıyla eşleşmeyen ondalık değerlerin "toplamının" görüntülenmesi.
D Stanley

1
Örnek durumunuzdaki "doğru" sonuç nedir? Bu, "en iyi" çözümün ne olduğu konusundaki anlaşmazlıkları çözebilir.
D Stanley

Yanıtlar:


35

Buradaki cevapların hiçbiri doğru şekilde çözülmediği için, burada underscorejs kullanan yarı gizlenmiş sürümüm :

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
Yanlışsam beni düzeltin, ama bu cevabım tarafından önerilen Algoritmanın bir uygulaması değil mi? (
Underscorejs

@VarunVohra üzgünüm, şimdiye kadar bunu fark etmedim, evet algoritmanız aynı gibi görünüyor :)
mesajım

@yonilevy Yorumumu sil; Ben sıralı bir liste dönmek gerekiyordu farkında değildi. Özür dilerim!
Zack Burt

2
Son eleman 0 olduğunda ve önceki elemanlar 100 olduğunda bu fonksiyonla ilgili bir sorun vardır. Örn. [52.6813880126183, 5.941114616193481, 24.55310199789695, 8.780231335436383, 8.04416403785489, 0]. Sonuncusu mantıksal olarak -1 değerini döndürür. Aşağıdaki çözümü gerçekten hızlı bir şekilde düşündüm ama muhtemelen daha iyi bir şey var: jsfiddle.net/0o75bw43/1
Cruclax

1
@Cruclax, giriş dizisindeki tüm girişler sıfır olduğunda 1 öğenin tümünü gösterir
tony.0919

159

Orijinal ondalık verilere güvenmekten endişe etmiyorsanız, bunu yapmanın birçok yolu vardır.

İlk ve belki de en popüler yöntem En Büyük Kalan Yöntem olacaktır.

Temel olarak:

  1. Her şeyi aşağı yuvarlama
  2. Toplam ve 100 arasındaki farkı elde etmek
  3. Ondalık parçalarını azalan sırada öğelere 1 ekleyerek farkı dağıtma

Sizin durumunuzda şöyle olur:

13.626332%
47.989636%
 9.596008%
28.788024%

Tam sayı parçalarını alırsanız,

13
47
 9
28

ki bu sayı 97'ye kadar çıkıyor ve üç tane daha eklemek istiyorsunuz. Şimdi, ondalık kısımlara bakıyorsunuz.

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

toplam 100'e ulaşıncaya kadar en büyüğünü alın. Böylece:

14
48
 9
29

Alternatif olarak, tamsayı değerleri yerine bir ondalık basamak göstermeyi de seçebilirsiniz. Yani sayılar 48.3 ve 23.9 vb. Olacaktır. Bu, varyansı 100'den çok düşürür.


5
Amerikan Matematik Derneği'nin web sitesinde yer alan bu "Özellik Sütunu" - Dağıtım II: Dağıtım Sistemleri - benzer birçok 'dağıtım' yöntemini açıklar.
Kenny Evitt

1
Bu neredeyse cevabımın bir kopyası ve yapıştırması gibi görünüyor stackoverflow.com/questions/5227215/… .
sawa

@DStanley'nin cevabı hakkındaki yorumunuzun aksine,% 9.596008'lik cevabınızda% 0.5'ten fazla olan% 9'a yuvarlanmıştır. Yine de iyi bir cevap.
Rolazaro Azeveires

33

Muhtemelen bunu yapmanın "en iyi" yolu ("en iyi" sübjektif bir terim olduğu için alıntılanmıştır) bulunduğunuz yerin (integral olmayan) bir çetelesini tutmak ve bu değeri yuvarlamaktır .

Daha sonra hangi değerin kullanılması gerektiğini belirlemek için bunu tarihle birlikte kullanın. Örneğin, verdiğiniz değerleri kullanarak:

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

Her aşamada, sayının kendisini yuvarlamazsınız. Bunun yerine, biriken değeri yuvarlar ve önceki taban çizgisinden bu değere ulaşan en iyi tamsayıyı hesaplarsınız - bu taban çizgisi, önceki satırın kümülatif değeridir (yuvarlanır).

Eğer çünkü bu işler değil her aşamada bilgi kaybı ziyade daha akıllıca bilgileri kullanarak. 'Doğru' yuvarlak değerler son sütundadır ve 100'e ulaştıklarını görebilirsiniz.

Bu ve her bir değeri körü körüne yuvarlama arasındaki farkı yukarıdaki üçüncü değerde görebilirsiniz. İken 9.596008olur normalde yuvarlak kadar 10birikmiş, 71.211976aşağı doğru mermi 71- Sadece bu yollarla 9önceki başlangıca eklemek için gereklidir 62.


Bu aynı zamanda üç roughly- gibi "sorunlu" dizisi için çalışan değerler, bir tanesi yuvarlanır edilmelidir: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
İkinci yaklaşım her iki sorunu da düzeltir. Birincisi 26, 25, 26, 23, ikincisini verir 1, 0, 1, 0, 1, 0, ....
paxdiablo

Bu yaklaşım, küçük sayıların yuvarlanması için de iyi sonuç verir, çünkü çıkış günahını negatif
saymayı

19

Yuvarlamanın amacı en az miktarda hata üretmektir. Tek bir değeri yuvarladığınızda, bu süreç basit ve kolaydır ve çoğu insan bunu kolayca anlar. Aynı anda birden çok sayıyı yuvarlarken, işlem daha da zorlaşır - hataların nasıl birleştirileceğini, yani neyin en aza indirileceğini tanımlamanız gerekir.

Varun Vohra tarafından iyi olarak cevap mutlak hataların toplamını minimize ve uygulamak çok basit. Bununla birlikte, ele almadığı kenar vakaları vardır - yuvarlamanın sonucu ne olmalıdır 24.25, 23.25, 27.25, 25.25? Bunlardan birinin aşağı yerine yuvarlanması gerekiyor. Muhtemelen listedeki ilk veya sonuncuyu keyfi olarak seçersiniz.

Belki de mutlak hata yerine bağıl hatayı kullanmak daha iyidir . 23,25'ten 24'e yuvarlama bunu% 3,2 değiştirirken 27,25'e 28 yuvarlama yalnızca% 2,8 değiştirir. Şimdi açık bir kazanan var.

Bunu daha da ince ayarlamak mümkündür. Bir ortak teknik etmektir kare büyük hatalar orantısız daha küçük olanlardan daha saymak böylece, her bir hatayı. Ayrıca göreceli hata almak için doğrusal olmayan bir bölen kullanırdım -% 1'deki bir hatanın% 99'daki bir hatadan 99 kat daha önemli olduğu doğru görünmüyor. Aşağıdaki kodda kare kökü kullandım.

Komple algoritma aşağıdaki gibidir:

  1. Yüzdeleri hepsini yuvarladıktan sonra toplayın ve 100'den çıkarın. Bu, bu yüzdelerin kaç tanesinin yuvarlanması gerektiğini gösterir.
  2. Her yüzde için, biri aşağı yuvarlandığında ve diğeri yuvarlandığında iki hata puanı oluşturun. İkisi arasındaki farkı ele alalım.
  3. Yukarıda üretilen hata farklılıklarını sıralayın.
  4. Yuvarlanması gereken yüzdelerin sayısı için, sıralı listeden bir öğe alın ve yuvarlanmış yüzdeyi 1 arttırın.

Örneğin, aynı hata toplamıyla birden fazla kombinasyonunuz olabilir 33.3333333, 33.3333333, 33.3333333. Bu kaçınılmazdır ve sonuç tamamen keyfi olacaktır. Aşağıda verdiğim kod, soldaki değerleri yuvarlamayı tercih ediyor.

Hepsini Python'da bir araya getirmek böyle görünüyor.

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]

Bu son örnekte de görebileceğiniz gibi, bu algoritma hala sezgisel olmayan sonuçlar sunabilmektedir. 89.0 hiçbir şekilde yuvarlama gerektirmemesine rağmen, listedeki değerlerden birinin yuvarlanması gerekiyordu; en düşük göreceli hata, çok daha küçük alternatifler yerine bu büyük değerin yuvarlanmasından kaynaklanır.

Bu cevap başlangıçta her yuvarlak yukarı / yuvarlak aşağı kombinasyonundan geçmeyi savunuyordu, ancak yorumlarda belirtildiği gibi daha basit bir yöntem daha iyi çalışıyor. Algoritma ve kod bu basitleştirmeyi yansıtır.


1
Süreci giden ağırlıklı hata damla azalan sırasına göre: Hepinizin kombinasyonları dikkate almak gerekir sanmıyorum sıfıra turda için sonsuza yuvarlak (hemen hemen sadece tanıtan tartı içine Verun Vohras en ve yonilevy yıllardan ( "özdeş") cevapları).
greybeard

@ greybeard haklısın, bunu düşünüyordum. Her değer için iki hata olduğu için sadece hatayı sıralayamadım , ancak farkı almak bu sorunu çözdü. Cevabı güncelledim.
Mark Ransom

Gerçek sayı% 0 olduğunda daima% 0 olmasını tercih ederim. Yani ekleme if actual == 0: return 0için error_genharika eserler.
Nikolay Baluk

1
ne isclosebaşında yöntem round_to_100?
toto_tico


7

Yuvarlak sayıları TOPLAMAYIN. Yanlış sonuçlar elde edeceksiniz. Toplam, terimlerin sayısına ve kesirli parçaların dağılımına bağlı olarak önemli ölçüde kapalı olabilir.

Ekran yuvarlak sayılar ancak özetlemek gerçek değerleri. Sayıları nasıl sunduğunuza bağlı olarak, bunu yapmanın gerçek yolu değişecektir. Bu şekilde elde edersiniz

 14
 48
 10
 29
 __
100

Gittiğiniz her şekilde tutarsızlık yaşarsınız. Örneğinizde, bir değeri yanlış şekilde "yuvarlamadan" 100'e kadar ekleyen sayıları göstermenin bir yolu yoktur (en az hata 9.596'dan 9'a değişecektir)

DÜZENLE

Aşağıdakilerden birini seçmeniz gerekir:

  1. Öğelerin doğruluğu
  2. Toplamın doğruluğu (yuvarlanmış değerleri topluyorsanız)
  3. Yuvarlanmış eşyalar ile yuvarlanan toplam arasındaki tutarlılık)

Çoğu zaman # 3 yüzdeyle uğraşırken en iyi seçenektir çünkü toplam, tek tek öğelerin toplamı 100'e eşit olmadığında% 101'e eşit olduğunda ve tek tek öğeleri doğru tuttuğunuzdan daha açıktır. "Yuvarlama" 9.596 ila 9 benim görüşüme göre yanlış.

Bunu açıklamak için bazen tek tek değerlerin yuvarlandığını ve toplam% 100 olmayabileceğini açıklayan bir dipnot ekliyorum - yuvarlamayı anlayan herkes bu açıklamayı anlayabilmelidir.


6
Basılı değerler 100'e kadar eklenmeyeceğinden çok yararlı değil. Sorunun amacı, kullanıcıların değerlerin yanlış olduğunu düşünmesini önlemekti, bu durumda, çoğu kişi toplamla karşılaştırıldığında ve karşılaştırırken yapacaktı .
vvohra87

@VarunVohra düzenlememi okuduğunda, sayılarınızı 0,5'ten fazla "yuvarlamadan" 100'e kadar eklenecek şekilde görüntüleyemezsiniz.
D Stanley

1
@DStanley aslında, tüm sayıların 0,5 utangaç olduğu bir kümeyi yasaklayarak yapabilirsiniz. Cevabımı kontrol et - LRM bunu yapıyor.
vvohra87

3
Orijinal örnek lrm olarak @VarunVohra 14, 48, 9 ve 29 olan 9'a 9,596 olacak "yuvarlak" Biz ediyorsanız verecektir tahsis tam sayılar dayalı LRM en doğru olacak, ama yine de daha fazla bir sonuç değişiyor yarım birimden.
D Stanley

7

Bir C # sürümü yuvarlama yardımcısı yazdım, algoritma Varun Vohra'nın cevabı ile aynı , umarım yardımcı olur.

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

Aşağıdaki Birim testini geçmektedir:

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

Güzel! bana başlamak için bir zemin verdi .. İnanıyorum olsa da Enumerable ForEach yok
Jack0fshad0ws

4

Yuvarlama nedeniyle hatanızı takip etmeyi ve sonra biriken hata geçerli sayının kesirli kısmından daha büyükse, taneye yuvarlamayı deneyebilirsiniz.

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

Bunun genel olarak işe yarayıp yaramayacağından emin değilim, ancak sipariş tersine çevrilirse benzer görünüyor:

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

Bunun bozulabileceği uç durumlar olduğundan eminim, ancak giriş verilerinizi temel olarak değiştirdiğiniz için herhangi bir yaklaşım en azından biraz keyfi olacaktır.


2
Muhasebeciler ve bankacılar yüzlerce yıldır benzer bir teknik kullanmaktadır. "Kalanı taşıyın" bir satırdan diğerine. "Taşıma" bir kuruş 1/2 ile başlayın. İlk değere "taşıma" ekleyin ve kısaltın. Şimdi keserek kaybettiğiniz miktar, "taşıma" koymak. Bunu sonuna kadar yapın, yuvarlak sayılar tam olarak her seferinde istenen toplamı toplar.
Jeff Grigg

Carolyn Kay, Access VB 2007'de bu uygulamayı önerdi: <code> '"Kalanı taşı" yöntemini kullanarak geri ödeme dolar ref1 = rsQry! [Ücretli Ücretli $$$] * rsQry! [Özellik Değeri] / propValTot ref2 = ref1 + ref5 'Taşınan geri kalanı ekleyin, sıfırı başlatmak için ref3 = ref2 * 100' ref4 = ref3 / 100 tamsayı sayısına 100 ile çarpın '100 ile ondalık sayıya bölün rsTbl! [Geri Ödeme Ücretli $$$] = ref4' Koy " kalan "tablodaki yuvarlak sayı ref5 = ref2 - ref4 'Yeni kalanını taşı </code>
Jeff Grigg

2

Bir keresinde, bir hedefe uyacak bir dizi sayıya asgari düzensizliği bulmak için bir yuvarlatılmış araç yazdım. Bu farklı bir sorundu, ancak teorik olarak burada benzer bir fikir kullanılabilirdi. Bu durumda, bir dizi seçeneğimiz var.

Böylece, ilk eleman için, ya 14'e ya da 13'e yuvarlayabiliriz. Bunu yapmanın maliyeti (ikili tamsayı programlama anlamında) yuvarlama için yuvarlamadan daha azdır, çünkü aşağı yuvarlama bu değeri daha büyük bir mesafeye taşıyın. Benzer şekilde, her sayıyı yukarı veya aşağı yuvarlayabiliriz, bu yüzden seçmemiz gereken toplam 16 seçenek vardır.

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

Normalde MATLAB'daki genel problemi burada, ikili bir tamsayı programlama aracı olan bintprog'u kullanarak çözerdim, ancak test edilecek sadece birkaç seçenek var, bu nedenle 16 alternatifin her birini test etmek için basit döngülerle yeterince kolaydır. Örneğin, bu seti şu şekilde yuvarlayacağımızı varsayalım:

 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

Yapılan toplam mutlak hata 1.25266'dır. Aşağıdaki alternatif yuvarlama ile hafifçe azaltılabilir:

 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

Aslında, bu mutlak hata açısından en uygun çözüm olacaktır. Tabii ki, 20 terim varsa, arama alanı 2 ^ 20 = 1048576 boyutunda olacaktır. 30 veya 40 terim için, bu alan önemli boyutta olacaktır. Bu durumda, belki bir dal ve bağlı şema kullanarak, alanı etkili bir şekilde arayabilen bir araç kullanmanız gerekir.


Yalnızca ileride başvurmak üzere: "en büyük kalan" algoritması, metriğinize göre toplam mutlak hatayı en aza indirmelidir (Bkz. Kanıt basit: varsayalım ki hatayı en aza indirmiyor. Sonra yuvarlandığı ve yuvarlanması gereken bir dizi değer olmalıdır (tam tersi (iki küme aynı boyuttadır). Ancak yuvarladığı her değer, bir sonraki tamsayıdan yuvarladığı (ve vv) değerin ötesindedir, bu nedenle yeni hata miktarı daha büyük olmalıdır. QED. Ancak, tüm hata metrikleri için çalışmaz; diğer algoritmalar gereklidir.
rici

2

Bence aşağıdakiler peşinde olduğunuz şeyi başaracak

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]

Son bir şey, istenen çıktıyla karşılaştırmak için soruda orijinal olarak verilen sayıları kullanarak işlevi çalıştırdım

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

Bu sorunun istediğinden farklıydı => [48, 29, 14, 9]. Toplam hata payına bakana kadar bunu anlayamadım

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

Aslında, benim fonksiyonumun sonucu aslında en az miktarda hata getiriyor.

Burada keman çal


Aklımda olan şey neredeyse buydu, hatanın değere göre ölçülmesi gerekiyor (9.8'den 10'a yuvarlama 19.8'den 20'ye yuvarlamadan daha büyük bir hatadır). Yine de bu, sıralama geri çağrısına yansıtarak kolayca yapılabilir.
15'te poezn

bu [33.33, 33.33, 33.33, 0.1] için yanlışsa, daha doğru olmaktan ziyade [1, 33, 33, 33] döndürür [34, 33, 33, 0]
yonilevy

@yonilevy Bunun için teşekkürler. Şimdi düzeltildi.
Bruno

henüz değil, [16.666, 16.666, 16.666, 16.666, 16.666, 16.666] için [16, 16, 17, 17, 17, 17] yerine [15, 17, 17, 17, 17, 17] döndürür - bakın cevap
yonilevy

2

Hangi doğruluk seviyesine ihtiyacınız olduğundan emin değilim, ancak yapacağım şey, ilk ondalık nsayıları eklemek n, ondalık toplamın tavanı olmak. Bu durumda 3, bu yüzden ilk 3 öğeye 1 ekler ve gerisini katlayacağım. Tabii ki bu süper doğru değil, bazı sayılar olmaması gerektiğinde yukarı veya aşağı yuvarlanabilir, ancak iyi çalışır ve her zaman% 100 ile sonuçlanır.

Yani [ 13.626332, 47.989636, 9.596008, 28.788024 ]olurdu [14, 48, 10, 28]çünkü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

Kullanıcılara her zaman sayıların yuvarlandığını ve süper doğru olmayabileceğini bildirebilirsiniz ...


1

Eğer yuvarlarsanız, her durumda tam olarak aynı şeyi almanın iyi bir yolu yoktur.

Sahip olduğunuz N yüzdelerinin ondalık kısmını alabilirsiniz (verdiğiniz örnekte 4'tür).

Ondalık kısımları ekleyin. Örneğinizde toplam kesirli kısım = 3 var.

En yüksek kesirlere sahip 3 sayıyı tavanlayın ve gerisini katlayın.

(Düzenlemeler için özür dilerim)


1
Bu 100'e kadar olan sayılar sağlayabilir, ancak 3.9'u 3'e ve
25.1'i

Hayır. 3.9 4 ve 25.1 25 olacaktır. İ en yüksek kesirleri en yüksek değeri değil 3 sayıyı tavana söyledi.
arunlalam

2
.9 ile biten çok fazla kesir varsa,% 9.9'luk 9 değer ve 10.9'luk bir değer var, bir değer% 9,% 8 ve% 10 gibi bir değer var.
arunlalam

1

Onları gerçekten yuvarlamanız gerekiyorsa, burada zaten çok iyi öneriler var (en büyük kalan, en az göreceli hata vb.).

Ayrıca, yuvarlanmamanın iyi bir nedeni var ("daha iyi görünüyor" ancak "yanlış" olan en az bir sayı alacaksınız) ve bunu nasıl çözeceğiniz (okuyucularınızı uyarın) ve yaptığım bu.

"Yanlış" numara kısmını eklememe izin verin.

Diyelim ki yaklaşık olarak yüzdelerinizle birlikte üç etkinliğiniz / varlığınız / var:

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

Daha sonra değerler biraz değişir,

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

İlk tablo, daha önce bahsedilen "yanlış" sayıya sahip olma sorununa sahiptir: 33.34, 33'e 34'ten daha yakındır.

Ama şimdi daha büyük bir hatanız var. 2. günden 1. güne kıyasla, A'nın gerçek yüzde değeri% 0.01 arttı, ancak yaklaşık% 1'lik bir düşüş gösterdi.

Bu niteliksel bir hatadır, muhtemelen ilk nicel hatadan çok daha kötüdür.

Tüm set için bir yaklaşım tasarlanabilir, ancak birinci günde veri yayınlamanız gerekebilir, böylece ikinci gün hakkında bilgi sahibi olmazsınız. Yani, gerçekten, gerçekten, yaklaşık olmadıkça, muhtemelen daha iyi değilsiniz.


daha iyi tablolar yapmak için nasıl bilmek ya düzenlemek ya da söyle bana nasıl / nerede
Rolazaro Azeveires

0

bunun geçerli olup olmadığını kontrol edin ya da benim test vakalarım kadar bu çalışmayı başarabiliyorum.

diyelim ki sayı k;

  1. oder'e göre yüzdeyi sırala.
  2. azalan sıradan her yüzde üzerinde yineleme.
  3. İlk yüzde için k yüzdesini hesaplayın.
  4. sonraki k = k-1
  5. tüm yüzde tüketilene kadar tekrarlayın.

0

Varun Vohra'nın hem listeler hem de dikteler için cevabından yöntemi uyguladım.

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 cevabının daha basit bir Python uygulaması:

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

İhtiyacınız math, itertools, operator.


0

Bir panda Serisinde yüzdelere sahip olanlar için, burada, yuvarlamak istediğiniz ondalıkları seçebileceğiniz en büyük kalan yöntem ( Varun Vohra'nın cevabında olduğu gibi) uygulamam.

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

Bu bankacının yuvarlanması, diğer bir deyişle 'yuvarlak yarım çift' için bir durumdur. BigDecimal tarafından desteklenir. Amacı, yuvarlamanın dengelenmesini sağlamaktır, yani banka ya da müşteri lehine değildir.


5
Yuvarlamanın dengelenmesini SAĞLAMAZ - sadece çift ​​ve tek sayılar arasında yarım yuvarlama dağıtarak hata miktarını azaltır . Yuvarlanan bankacıların yanlış sonuçlar verdiği senaryolar hala var.
D Stanley

@DStanley Kabul etti. Başka türlü söylemedim. Ben amacını söyledim . Çok dikkatli.
Lorne Marquis

2
Yeterince adil - Söylemeye çalıştığın şeyi yanlış yorumladım. Her iki durumda da bankacılar yuvarlama kullanarak örnekte sonuçları değişmez gibi sorunu çözdü sanmıyorum.
D Stanley
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.