如何在保留总和的同时将浮点数舍入为整数?

时间:2009-04-27 06:53:16

标签: algorithm language-agnostic rounding

假设我有一个浮点数数组,按排序(让我们说升序)顺序,其总和已知为整数N。我希望将这些数字“舍入”为整数,同时保持其总和不变。换句话说,我正在寻找一种算法,将浮点数的数组(称为fn)转换为整数数组(称之为in),以便:

  1. 这两个数组具有相同的长度
  2. 整数数组的总和为N
  3. 每个浮点数fn[i]与其对应的整数in[i]之间的差异小于1(或者如果你真的必须等于1)
  4. 鉴于浮点数按排序顺序(fn[i] <= fn[i+1]),整数也将按排序顺序排列(in[i] <= in[i+1]
  5. 鉴于满足这四个条件,最好将舍入方差(sum((in[i] - fn[i])^2))最小化的算法,但这不是什么大问题。

    示例:

    [0.02, 0.03, 0.05, 0.06, 0.07, 0.08, 0.09, 0.1, 0.11, 0.12, 0.13, 0.14]
        => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
    [0.1, 0.3, 0.4, 0.4, 0.8]
        => [0, 0, 0, 1, 1]
    [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]
        => [0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
    [0.4, 0.4, 0.4, 0.4, 9.2, 9.2]
        => [0, 0, 1, 1, 9, 9] is preferable
        => [0, 0, 0, 0, 10, 10] is acceptable
    [0.5, 0.5, 11]
        => [0, 1, 11] is fine
        => [0, 0, 12] is technically not allowed but I'd take it in a pinch
    

    回答评论中提出的一些优秀问题:

    • 两个数组都允许重复元素(虽然我也有兴趣听说只有浮点数不包含重复的算法才有效)
    • 没有一个正确的答案 - 对于给定的浮点数输入数组,通常有多个int数组满足这四个条件。
    • 我想到的应用程序 - 这有点奇怪 - 在MarioKart游戏中向顶级终结者分发点数;-)从未真正玩过这个游戏,但是在看别人的时候我注意到有24个在前4名选手中分配了分数,我想知道如何根据完成时间分配分数(所以如果有人以较大的领先优势完成分数,他们会获得更大的分数)。游戏将点总数跟踪为整数,因此需要进行这种舍入。

    对于好奇,这里是the test script我曾经确定哪些算法有效。

13 个答案:

答案 0 :(得分:27)

您可以尝试的一个选项是“级联舍入”。

对于此算法,您可以跟踪两个运行总计:到目前为止的一个浮点数,以及到目前为止的一个整数。 要获得下一个整数,请将下一个fp编号添加到运行总计中,然后舍入运行总计,然后从舍入的运行总计中减去整数运行总计: -

number  running total   integer integer running total
   1.3       1.3          1           1
   1.7       3.0          2           3
   1.9       4.9          2           5
   2.2       8.1          3           8
   2.8      10.9          3          11
   3.1      14.0          3          14

答案 1 :(得分:17)

这是一个应该完成任务的算法。与其他算法的主要区别在于,这个算法总是以正确的顺序对数字进行舍入。 最大限度地减少出现错误。

该语言是一些伪语言,可能源自JavaScript或Lua。应该解释一下。注意一个基于索引(对于循环,x和y更好。:p)

// Temp array with same length as fn.
tempArr = Array(fn.length)

// Calculate the expected sum.
arraySum = sum(fn)

lowerSum = 0
-- Populate temp array.
for i = 1 to fn.lengthf
    tempArr[i] = { result: floor(fn[i]),              // Lower bound
                   difference: fn[i] - floor(fn[i]),  // Roundoff error
                   index: i }                         // Original index

    // Calculate the lower sum
    lowerSum = lowerSum + tempArr[i].result
end for

// Sort the temp array on the roundoff error
sort(tempArr, "difference")

// Now arraySum - lowerSum gives us the difference between sums of these
// arrays. tempArr is ordered in such a way that the numbers closest to the
// next one are at the top.
difference = arraySum - lowerSum

// Add 1 to those most likely to round up to the next number so that
// the difference is nullified.
for i = (tempArr.length - difference + 1) to tempArr.length
    tempArr.result = tempArr.result + 1
end for

// Optionally sort the array based on the original index.
array(sort, "index")

答案 2 :(得分:16)

一个非常简单的方法是获取所有小数部分并总结它们。根据您的问题定义,该数字必须是整数。从最大的数字开始均匀地分配整数。然后给一个第二大数字...等等,直到你用完了要分发的东西。

注意这是伪代码...并且可能在索引中被一个人关闭......它已经晚了,我很困。

float accumulator = 0;

for (i = 0; i < num_elements; i++)  /* assumes 0 based array */
{
   accumulator += (fn[i] - floor(fn[i])); 
   fn[i] =  (fn[i] - floor(fn[i]);
}

i = num_elements;

while ((accumulator > 0) && (i>=0))
{
    fn[i-1] += 1;   /* assumes 0 based array */
    accumulator -= 1;
    i--;
}

更新:根据对每个值执行了多少截断,还有其他分配累积值的方法。这需要保留一个名为loss [i] = fn [i] - floor(fn [i])的单独列表。然后,您可以重复fn [i]列表并重复给出最大损失项1(之后将损失[i]设置为0)。它很复杂,但我猜它有用。

答案 3 :(得分:4)

怎么样:

a) start: array is [0.1, 0.2, 0.4, 0.5, 0.8], N=3, presuming it's sorted
b) round them all the usual way: array is [0 0 0 1 1]
c) get the sum of the new array and subtract it from N to get the remainder.
d) while remainder>0, iterate through elements, going from the last one
   - check if the new value would break rule 3.
   - if not, add 1
e) in case that remainder<0, iterate from first one to the last one
   - check if the new value would break rule 3.
   - if not, subtract 1

答案 4 :(得分:3)

基本上你要做的就是在四舍五入后将残羹剩饭分配给最有可能的候选人。

  1. 像往常一样对浮点数进行舍入,但跟踪舍入和相关索引的增量到fnin
  2. 按delta添加第二个数组。
  3. sum(in) < N时,从最大的负增量向前工作,增加舍入值(确保您仍然满足规则#3)。
  4. 或者,当sum(in) > N时,从最大正增量向后工作,减少舍入值(确保您仍然满足规则#3)。
  5. 示例:

    [0.02, 0.03, 0.05, 0.06, 0.07, 0.08, 0.09, 0.1, 0.11, 0.12, 0.13, 0.14] N=1
    
    1. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] sum=0
    and [[-0.02, 0], [-0.03, 1], [-0.05, 2], [-0.06, 3], [-0.07, 4], [-0.08, 5], 
         [-0.09, 6], [-0.1, 7], [-0.11, 8], [-0.12, 9], [-0.13, 10], [-0.14, 11]]
    
    2. sorting will reverse the array
    
    3. working from the largest negative remainder, you get [-0.14, 11].
    Increment `in[11]` and you get [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1] sum=1 
    Done.

答案 5 :(得分:1)

你能尝试这样的事吗?

in [i] = fn [i] - int (fn [i]);
fn_res [i] = fn [i] - in [i];

fn_res→是结果分数。 (我认为这是基本的......),我们错过了什么吗?

答案 6 :(得分:1)

嗯,4是痛点。否则你可以做一些事情,比如“通常向下舍入并累积剩余;当累加器&gt; = 1时向上舍入”。 (编辑:实际上,只要你交换了他们的位置,这可能仍然可以吗?)

使用线性编程可能有办法吗? (这是数学“编程”,而不是计算机编程 - 你需要一些数学来找到可行的解决方案,尽管你可能会跳过通常的“优化”部分。)

作为线性编程的一个例子 - 用例子[1.3,1.7,1.9,2.2,2.8,3.1]你可以有规则:

1 <= i < 2
1 <= j < 2
1 <= k < 2
2 <= l < 3
3 <= m < 4
i <= j <= k <= l <= m
i + j + k + l + m = 13

然后应用一些线性/矩阵代数;-p提示:有些产品可以根据“Simplex”算法来完成上述操作。常见的大学饲料(我在大学为我的最终项目写了一篇)。

答案 7 :(得分:1)

我认为问题在于没有指定排序算法。或者更像 - 无论是否稳定。

考虑以下浮点数组:

[0.2 0.2 0.2 0.2 0.2]

总和为1.整数数组应为:

[0 0 0 0 1]

但是,如果排序算法不稳定,它可以在数组中的其他位置对“1”进行排序......

答案 8 :(得分:0)

使求和的差异小于1,并检查是否排序。 有些人喜欢,

while(i < sizeof(fn) / sizeof(float)) {
    res += fn[i] - floor(fn[i]);
    if (res >= 1) {
        res--;
        in[i] = ceil(fn[i]);
    }
    else
        in[i] = floor(fn[i]);
    if (in[i-1] > in[i])
        swap(in[i-1], in[i++]);
}

(这是纸质代码,所以我没有检查有效性。)

答案 9 :(得分:0)

下面是@ mikko-rantanen代码的python和numpy实现。我花了一些时间将它们放在一起,所以尽管主题时代已经存在,但这可能对未来的Google员工有所帮助。

import numpy as np
from math import floor

original_array = np.array([1.2, 1.5, 1.4, 1.3, 1.7, 1.9])

# Calculate length of original array
# Need to substract 1, as indecies start at 0, but product of dimensions
# results in a count starting at 1
array_len = original_array.size - 1 # Index starts at 0, but product at 1

# Calculate expected sum of original values (must be integer)
expected_sum = np.sum(original_array)

# Collect values for temporary array population
array_list = []
lower_sum = 0
for i, j in enumerate(np.nditer(original_array)):
    array_list.append([i, floor(j), j - floor(j)]) # Original index, lower bound, roundoff error
# Calculate the lower sum of values
lower_sum += floor(j)

# Populate temporary array
temp_array = np.array(array_list)

# Sort temporary array based on roundoff error
temp_array = temp_array[temp_array[:,2].argsort()]

# Calculate difference between expected sum and the lower sum
# This is the number of integers that need to be rounded up from the lower sum
# The sort order (roundoff error) ensures that the value closest to be
# rounded up is at the bottom of the array
difference = int(expected_sum - lower_sum)

# Add one to the number most likely to round up to eliminate the difference
temp_array_len, _ = temp_array.shape
for i in xrange(temp_array_len - difference, temp_array_len):
    temp_array[i,1] += 1

# Re-sort the array based on original index
temp_array = temp_array[temp_array[:,0].argsort()]

# Return array to one-dimensional format of original array
array_list = []
for i in xrange(temp_array_len):
    array_list.append(int(temp_array[i,1]))
new_array = np.array(array_list)

答案 10 :(得分:0)

计算sum of floorsum of numbers。 轮次sum of numbers,减去sum of floor,不同之处在于我们需要修补多少天花板(我们需要多少+1)。 使用天花板与数字的差异对数组进行排序,从小到大。

对于diff次(diff我们需要修补多少天花板),我们将结果设置为ceiling of number。其他人将结果设置为floor of numbers

public class Float_Ceil_or_Floor {

public static int[] getNearlyArrayWithSameSum(double[] numbers) {

    NumWithDiff[] numWithDiffs = new NumWithDiff[numbers.length];
    double sum = 0.0;
    int floorSum = 0;
    for (int i = 0; i < numbers.length; i++) {
        int floor = (int)numbers[i];
        int ceil = floor;
        if (floor < numbers[i]) ceil++; // check if a number like 4.0 has same floor and ceiling
        floorSum += floor;
        sum += numbers[i];
        numWithDiffs[i] = new NumWithDiff(ceil,floor, ceil - numbers[i]);
    }

    // sort array by its diffWithCeil
    Arrays.sort(numWithDiffs, (a,b)->{
        if(a.diffWithCeil < b.diffWithCeil)  return -1;
        else return 1;
    });

    int roundSum = (int) Math.round(sum);
    int diff = roundSum - floorSum;
    int[] res = new int[numbers.length];

    for (int i = 0; i < numWithDiffs.length; i++) {
        if(diff > 0 && numWithDiffs[i].floor != numWithDiffs[i].ceil){
            res[i] = numWithDiffs[i].ceil;
            diff--;
        } else {
            res[i] = numWithDiffs[i].floor;
        }
    }
    return res;
}
public static void main(String[] args) {
    double[] arr = { 1.2, 3.7, 100, 4.8 };
    int[] res = getNearlyArrayWithSameSum(arr);
    for (int i : res) System.out.print(i + " ");

}

}

class NumWithDiff {
    int ceil;
    int floor;
    double diffWithCeil;
    public NumWithDiff(int c, int f, double d) {
        this.ceil = c;
        this.floor = f;
        this.diffWithCeil = d;
    }
}

答案 11 :(得分:0)

在不减少差异的情况下,这是一个微不足道的:

  1. 从左到右排序值。
  2. 将所有内容舍入到下一个整数。
  3. 让这些整数之和为K.将N-K最右边的值增加1。
  4. 恢复原始订单。
  5. 这显然满足了你的条件1.-4。或者,您可以舍入到最接近的整数,并增加您向下舍入的N-K。您可以通过原始值和舍入值之间的差异来贪婪地执行此操作,但每次向下舍入的值都必须从右向左增加,以维持排序顺序。

答案 12 :(得分:0)

如果您可以接受总计的微小变化,同时改善差异,则可以将总计保留在python中:

import math
import random
integer_list = [int(x) + int(random.random() <= math.modf(x)[0]) for x in my_list]

解释为将所有数字四舍五入,然后将一个数字与小数部分相加,即十分之一的0.1将变成1,其余的0

这适用于统计数据,其中您将大量小数人转换为1人或0人