考虑下面四个百分比,用浮点数表示:

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


当前回答

不要把四舍五入的数字相加。你会得到不准确的结果。总数可能会显著偏离,这取决于术语的数量和小数部分的分布。

显示四舍五入的数字,但和实际值。根据你呈现数字的方式不同,实际的方法也会有所不同。这样你就能得到

14 48 10 29 __ 100

不管怎样,都会有差异。在你的例子中,没有办法显示加起来等于100的数字而不以错误的方式“舍入”一个值(最小的错误是将9.596更改为9)

EDIT

你需要在以下选项中做出选择:

项目的准确性 和的准确性(如果你是四舍五入的值) 四舍五入的项目与四舍五入的总和的一致性)

大多数情况下,当处理百分比时,第三种方法是最好的选择,因为当总数等于101%时比当单个项目的总数不等于100时更明显,并且您可以保持单个项目的准确性。“舍入”9.596到9在我看来是不准确的。

为了解释这一点,我有时会添加一个脚注,解释各个值是四舍五入的,可能不是100% -任何理解四舍五入的人都应该能够理解这个解释。

其他回答

如果是四舍五入,就没有办法在所有情况下都得到完全相同的结果。

你可以取你拥有的N个百分比的小数部分(在你给出的例子中是4)。

把小数部分相加。在你的例子中,总分为3。

将分数最高的3个数字上排,其余的取底。

(抱歉修改了)

我不确定你需要什么程度的精度,但我要做的就是简单地把前n个数字加1,n是小数总和的上界。在这种情况下,它是3,所以我将给前3项加1,然后将其余的取整。当然,这并不是非常准确,有些数字可能会四舍五入或在不应该的时候,但它工作得很好,总是会得到100%。

因此[13.626332,47.989636,9.596008,28.788024]将是[14,48,10,28],因为Math.ceil(.626332+.989636+.596008+.788024) == 3

function evenRound( arr ) {
  var decimal = -~arr.map(function( a ){ return a % 1 })
    .reduce(function( a,b ){ return a + b }); // Ceil of total sum of decimals
  for ( var i = 0; i < decimal; ++i ) {
    arr[ i ] = ++arr[ i ]; // compensate error by adding 1 the the first n items
  }
  return arr.map(function( a ){ return ~~a }); // floor all other numbers
}

var nums = evenRound( [ 13.626332, 47.989636, 9.596008, 28.788024 ] );
var total = nums.reduce(function( a,b ){ return a + b }); //=> 100

你总是可以告诉用户这些数字是四舍五入的,可能不是非常准确……

如果你只有两个选项,你可以使用Math.round()。唯一有问题的值对是X.5(例如;37.5和62.5)它会四舍五入两个值,你最终会得到101%,你可以试试这里:

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

因为你需要始终显示100%,你只需从它们中删除一个百分比,例如在第一个

const correctedar= Number.isInteger(around -0.5) ?A - 1: A

或者你可以选择有更多%选票的选项。

1% diff的错误在1-100对值的划分的10k例中发生114次。

不要把四舍五入的数字相加。你会得到不准确的结果。总数可能会显著偏离,这取决于术语的数量和小数部分的分布。

显示四舍五入的数字,但和实际值。根据你呈现数字的方式不同,实际的方法也会有所不同。这样你就能得到

14 48 10 29 __ 100

不管怎样,都会有差异。在你的例子中,没有办法显示加起来等于100的数字而不以错误的方式“舍入”一个值(最小的错误是将9.596更改为9)

EDIT

你需要在以下选项中做出选择:

项目的准确性 和的准确性(如果你是四舍五入的值) 四舍五入的项目与四舍五入的总和的一致性)

大多数情况下,当处理百分比时,第三种方法是最好的选择,因为当总数等于101%时比当单个项目的总数不等于100时更明显,并且您可以保持单个项目的准确性。“舍入”9.596到9在我看来是不准确的。

为了解释这一点,我有时会添加一个脚注,解释各个值是四舍五入的,可能不是100% -任何理解四舍五入的人都应该能够理解这个解释。

我写了一个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 });
}