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

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


当前回答

如果你只有两个选项,你可以使用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次。

其他回答

我用Javascript写了一个函数,它接受一个百分比数组,并使用最大余数方法输出一个四舍五入的百分比数组。它不使用任何库。

输入:[21.6,46.7,31,0.5,0.2]

输出:[22,47,31,0,0]

const values = [21.6, 46.7, 31, 0.5, 0.2]; console.log(roundPercentages(values)); function roundPercentages(values) { const flooredValues = values.map(e => Math.floor(e)); const remainders = values.map(e => e - Math.floor(e)); const totalRemainder = 100 - flooredValues.reduce((a, b) => a + b); // Deep copy because order of remainders is important [...remainders] // Sort from highest to lowest remainder .sort((a, b) => b - a) // Get the n largest remainder values, where n = totalRemainder .slice(0, totalRemainder) // Add 1 to the floored percentages with the highest remainder (divide the total remainder) .forEach(e => flooredValues[remainders.indexOf(e)] += 1); return flooredValues; }

如果你真的必须四舍五入,这里已经有了很好的建议(最大余数,最小相对误差,等等)。

也有一个很好的理由不四舍五入(你至少会得到一个“看起来更好”但“错误”的数字),以及如何解决这个问题(警告你的读者),这就是我所做的。

让我加上“错误”的数字部分。

假设你有三个事件/实体/…用一些百分比来近似:

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

这是一个定性错误,可能比最初的定量错误更严重。

你可以为整个集合设计一个近似值,但是,你可能必须在第一天发布数据,因此你不知道第二天的情况。所以,除非你真的,真的,必须近似,否则最好不要。

我已经实现了Varun Vohra的答案在这里的列表和字典的方法。

import math
import numbers
import operator
import itertools


def round_list_percentages(number_list):
    """
    Takes a list where all values are numbers that add up to 100,
    and rounds them off to integers while still retaining a sum of 100.

    A total value sum that rounds to 100.00 with two decimals is acceptable.
    This ensures that all input where the values are calculated with [fraction]/[total]
    and the sum of all fractions equal the total, should pass.
    """
    # Check input
    if not all(isinstance(i, numbers.Number) for i in number_list):
        raise ValueError('All values of the list must be a number')

    # Generate a key for each value
    key_generator = itertools.count()
    value_dict = {next(key_generator): value for value in number_list}
    return round_dictionary_percentages(value_dict).values()


def round_dictionary_percentages(dictionary):
    """
    Takes a dictionary where all values are numbers that add up to 100,
    and rounds them off to integers while still retaining a sum of 100.

    A total value sum that rounds to 100.00 with two decimals is acceptable.
    This ensures that all input where the values are calculated with [fraction]/[total]
    and the sum of all fractions equal the total, should pass.
    """
    # Check input
    # Only allow numbers
    if not all(isinstance(i, numbers.Number) for i in dictionary.values()):
        raise ValueError('All values of the dictionary must be a number')
    # Make sure the sum is close enough to 100
    # Round value_sum to 2 decimals to avoid floating point representation errors
    value_sum = round(sum(dictionary.values()), 2)
    if not value_sum == 100:
        raise ValueError('The sum of the values must be 100')

    # Initial floored results
    # Does not add up to 100, so we need to add something
    result = {key: int(math.floor(value)) for key, value in dictionary.items()}

    # Remainders for each key
    result_remainders = {key: value % 1 for key, value in dictionary.items()}
    # Keys sorted by remainder (biggest first)
    sorted_keys = [key for key, value in sorted(result_remainders.items(), key=operator.itemgetter(1), reverse=True)]

    # Otherwise add missing values up to 100
    # One cycle is enough, since flooring removes a max value of < 1 per item,
    # i.e. this loop should always break before going through the whole list
    for key in sorted_keys:
        if sum(result.values()) == 100:
            break
        result[key] += 1

    # Return
    return result

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

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

14 48 10 29 __ 100

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

EDIT

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

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

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

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

只要您不关心对原始十进制数据的依赖,就有许多方法可以做到这一点。

第一种也是最流行的方法是最大余数法

基本上就是:

四舍五入 求sum和100的差值 将差值按小数部分的递减顺序加1

在你的例子中,它是这样的:

13.626332%
47.989636%
 9.596008%
28.788024%

如果取整数部分,就得到

13
47
 9
28

加起来是97,再加3。现在,你看小数点部分

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

取最大的,直到总数达到100。所以你会得到:

14
48
 9
29

或者,您可以简单地选择显示一个小数位而不是整数值。所以数字是48.3和23.9等等。这会使方差从100下降很多。