Skip to content

无尾差计算百分比

目录


4 个项目合成一个总数

然后得到这 4 个项目占总数的百分比,并展示

业务常见类似下面这样

tsx
const total = list.reduce((acc, cur) => acc + cur.value, 0);

/// ....

return (
  <div>
    {list.map((item) => {
      return (
        <div>
          <div>{item.name}</div>
          <div>{item.value}</div>
          <div>{((item.value / total) * 100).toFixed(2)}</div>
        </div>
      );
    })}
  </div>
);

但是存在尾差问题,即四个项目的百分比相加不等于 100%

已知百分比获取的时候是四舍五入取 2 位小数(由 toFixed()实现)

列出数据

> (282/25995*100).toFixed(6)
'1.084824'
> (1357/25995*100).toFixed(6)
'5.220235'
> (963/25995*100).toFixed(6)
'3.704559'
> (23393/25995*100).toFixed(6)
'89.990383'

发现没有一个数据会触发“五入”(小数点后第三位都<5)导致出现尾差

小数点 3 位以及之后的值加起来应该 = 0.01 也就是差去的数

对于这些百分比的小数后第三为 ,存在 3 种情况

  1. 其中一个>5,产生进位,那么尾差就会被弥补,不用担心
  2. 对于所有都>5,无法产生进位,导致尾差产生,需要考虑
  3. 存在多个>5,产生过多的进位

最合理的方式是对小数点 3 位以及之后的值最大的百分比进位,但是实现起来比较麻烦

目前的解决方案是,最后一个值不由 item.value / total而是 100-前面所有百分比的和

当然也可以在外第一一个值,逐个比较,记录最大的值,然后进位,但是不够优雅

tsx
const total = list.reduce((acc, cur) => acc + cur.value, 0);

/// ....

<div>
  {list.map((item) => {
    return (
      <div>
        <div>{item.name}</div>
        <div>{item.value}</div>
        <div>{((item.value / total) * 100).toFixed(2)}</div>
      </div>
    );
  })}
</div>;

相对优雅的方案

tsx
const total = list.reduce((acc, cur) => acc + cur.value, 0);
const percent_max = list.reduce((acc, item, index) => {
  if (acc == -1) return acc;
  const percent = ((item.value / total) * 100000) % 10;
  if (percent >= 5) {
    return -1;
  } else {
    const percent2 = ((list[acc].value / total) * 100000) % 10;
    return percent > percent2 ? index : acc;
  }
}, 0);
/// ....

<div>
  {list.map((item) => {
    const pec = (
      (item.value / total) * 100 +
      (index == percent_max ? 0.01 : 0)
    ).toFixed(2);
    return (
      <div>
        <div>{item.name}</div>
        <div>{item.value}</div>
        <div>{pec}</div>
      </div>
    );
  })}
</div>;

但是这样无法解决 3 的问题,今天脑子用完了,下次再说

Copyright © 2022 田园幻想乡 浙ICP备2021038778号-1