给定数量的数组“分布/比例”到特定值

时间:2019-11-20 16:47:55

标签: javascript arrays typescript decimal scale

我正在一个项目中,我需要分配一个数组的值,这些值的总和必须为100。

示例:

  • [70, 34, 92]和总值100。输出为[35.71, 17.35, 46.94],因为35.71 + 17.35 + 46.94 = 100
  • [86, 99.5, 100]和总值100。输出为[30.12, 34.85, 35.03]
  • [96, 37]和总值100。输出为[72.18, 27.82]
  • [98, 76.5, 68.5, 63.5, 38.5]和总值100。输出为[28.41, 22.17, 19.86, 18.41, 11.15] = 100(sum)。

到目前为止,我的解决方案是

  • 对给定数组中的所有值求和
  • 每个值乘以100
  • 将每个值除以总和
  • 使用.toFixed(2)

“公式/计算”:+(((valueOfArray * 100) / totalSumOfGivenArray).toFixed(2))

我的第一个解决方案(有小数点的问题,所以不可接受):

prioritySum  = 0;
controlsKey.forEach((value) => {
      priorityVal = this.getDistributedValue(+form.controls[formId].value, totalCatSum); // return +(((valueOfArray * 100) / totalSumOfGivenArray).toFixed(2))
      prioritySum = prioritySum + priorityVal;
});

此解决方案有时不能完全返回100值。输出各种内容,例如99.99、99.999999、100(大部分),100.0000000001、99.999978、100.09999999、99.000009。此处的十进制数字存在一些问题。

我使用的另一种方法是:

let i = 0;
for(i = 0;  i < controlsKey.length-1; i++){
  let formId = controlsKey[i];
  relValue = this.getDistributedValue(+form.controls[formId].value, totalActualSum); // returns formula result
  form.controls[formId].setValue(relValue))
  relativeSum = relativeSum + relValue;
}
relValue = 100 - relativeSum;
form.controls[controlsKey[i]].setValue(relValue)

这很好,但是这个解决方案嗯...

所以我的问题是否有解决这个问题的优雅方法?

第一种解决方案对我来说还可以,但是即使我使用了.toFixed(2)

,小数也有一些问题

2 个答案:

答案 0 :(得分:1)

因此,最大的问题是浮点数固有的不准确性。如果您使用toFixed,则无法解决的问题-即使您确定了错误,然后选择一个元素并将其增加/减少错误的数量,您也会只需沿 other 方向减去该量,因为浮点数不能在所有操作中保持必要的精度:

function getDistribution(values) {
    var sum =  values.reduce((carry, current) => carry + current, 0)
    var distribution = values.map(v => 100 * v / sum)
    var err = distribution.reduce((carry, current) => carry+current, 0) - 100;
    console.log({distribution, err});
    distribution[0] -= err;
    err = distribution.reduce((carry, current) => carry+current, 0) - 100;
    console.log({distribution, err});
}

getDistribution([98, 76.5, 68.5, 63.5, 38.5]);

答案 1 :(得分:1)

为防止出现不必要的浮点值,可以使用整数值并从基数10000(100和两个位置)中减去该值,并创建一个字符串数组,在该字符串数组中将点插入到获取格式化的字符串。

const nice = s => s.toString().replace(/\d\d$/, '.$&');

function get100(array) {
    var sum = array.reduce((a, b) => a + b),
        offset = 1e4;
    return array.map((v, j, { length }) => {
        if (j + 1 === length) return nice(offset);
        var i = Math.round(v * 1e4 / sum);
        offset -= i;
        return nice(i);
    });
}

console.log(...get100([70, 34, 92])); // [35.71, 17.35, 46.94] 
console.log(...get100([86, 99.5, 100])); // [30.12, 34.85, 35.03].
console.log(...get100([96, 37])); // [72.18, 27.82].
console.log(...get100([98, 76.5, 68.5, 63.5, 38.5])); // [28.41, 22.17, 19.86, 18.41, 11.15]