如何根据价格添加自定义舍入规则

How do I add custom rounding rules depending on the price

我可能只见树木不见森林,但我在根据自定义规则集对价格的整数和小数进行舍入时遇到问题。

示例:

100-200 之间的所有价格(以美元为单位)应四舍五入为 50 或 90 美分。此外,该点左侧的数字应增加到最接近的数字除以 5.

下面是我设置的数据结构,我想我可能会用它来计算舍入。

const data = {
  "us": [
    {
      "start": 100,
      "end": 200,
      "integer": 5,
      "decimals": [
        {"start": 0, "end": 50, "value": 50},
        {"start": 50, "end": 90, "value": 90},
      ]
    }
  ]
};

// I have made price a string so I can do split(), is there a better way?
const price = '101.10';
let priceParts = price.split('.');

for (const item of data.us) {
  if (priceParts[0] >= item.start && priceParts[0] <= item.end) {
    // Round up priceParts[0] with item.integer
    // Obviously below is incorrect, but I was hoping you guys have a neat function for it
    priceParts[0] = Number(priceParts[0]) + item.integer

    for (const decimal of item.decimals) {
      if (priceParts[1] <= decimal.start && priceParts[1] >= decimal.end) {
        // Round up priceParts[1] with decimal.value
        priceParts[1] = decimal.value;
      }
    }
  }
}

const newPrice = Number(priceParts.join('.')).toFixed(2);

// newPrice = 106.90
console.log(newPrice);

整数部分为parseInt(price),小数部分为价格减去整数部分。然后对于小数部分,你只需要找到与边界匹配的第一组,并使用这些值来调整它。

类似

const data = {
  "us": [{
    "start": 100,
    "end": 200,
    "integer": 5,
    "decimals": [{
        "start": 0,
        "end": 50,
        "value": 50
      },
      {
        "start": 50,
        "end": 90,
        "value": 90
      },
    ]
  }]
};

const price = 101.10;
let integerPart = parseInt(price);
let decimalPart = (price - integerPart) * 100;

for (const item of data.us) {
  if (integerPart > item.start && integerPart <= item.end) {
    const needsAdjustment = integerPart % item.integer > 0;
    
    if (needsAdjustment) {
      integerPart = (parseInt(integerPart / item.integer) + 1) * item.integer
    }

    const decimalLimitGroup = item.decimals.find(decimalLimit => decimalPart >= decimalLimit.start && decimalPart <= decimalLimit.end);

    if (decimalLimitGroup) {
      decimalPart = decimalLimitGroup.value
    }
  }
}

const newPrice = (integerPart + (decimalPart / 100)).toFixed(2);

// newPrice = 106.90
console.log(newPrice);

你用下面的行“四舍五入”到以下 item.integer

priceParts[0] = Number(priceParts[0]) + item.integer

唯一缺少的是整数除以 item.integer 时的余数。

const integer   = Number(priceParts[0]);
const remainder = integer % item.integer;
priceParts[0]   = integer + item.integer - remainder;

我们来看一个例子:

// say number = 12 and you want to round up to multiples of 5
const remainder = 12 % 5;     // 2
const result    = 12 + 5 - 2; // 15

上述解决方案存在两个问题。如果余数是 0,它出现在 0、5、10、15 等处,您不想将 5 添加到数字中。否则 10 将四舍五入为 1515 将四舍五入为 20.

因此,如果余数是 0(假),我们只使用数字,因为它已经可以被 item.integer 整除。意思是上面变成了:

const integer   = Number(priceParts[0]);
const remainder = integer % item.integer;

if (remainder) {
  priceParts[0] = integer + item.integer - remainder;
} else {
  priceParts[0] = integer;
}

另一个问题是上面假设输入数字是正数。如果可以提供负数,则必须更改:

const remainder = integer % item.integer;

进入:

const modulo = ((integer % item.integer) + item.integer) % item.integer;

这是因为 -12 确实有 -2 的余数,所以如果您遵循相同的原则,您将得到 -12 + 5 - -2 = -5。对于负数,我们需要取模。 -12 的模是 3 导致 -12 + 5 - 3 = -10.

详情请参阅 remainder operator 的描述。


一个更简单的解决方案是使用 Math.floor()Math.round()Math.ceil() 中的内置程序为您进行四舍五入。因为它四舍五入到最接近的整数,所以我们必须将整数转换为“5 的倍数”数字,对其进行四舍五入,最后将其转换回普通整数。

priceParts[0] = Math.ceil(Number(priceParts[0]) / item.integer) * item.integer;

这里的好处是 item.integer(此处为 5)的倍数或负数没有奇怪的舍入情况。


随着整数四舍五入的方式,我个人会使用不同的方法。您可以完全使用数字值而不是专注于小数的字符串表示。

您可以使用 Math.floor(number) 获取数字的整数部分,您可以使用 (number - integer) * 100 获取小数部分。如果将小数点除以 100,则可以将小数点加到整数上,而不是将它们连接在一起。

下面的代码片段使用 find() 搜索规则数组以找到符合条件的规则。

function createRound(rules) {
  return function round(num) {
    const int = Math.floor(num);
    const dec = (num - int) * 100;

    const rule = rules.find(({start, end}) => num >= start && num <= end);
    if (!rule) return num; // or throw error
    
    const decRule = rule.decimals
      .find(({start, end}) => dec >= start && dec <= end)
      || { value: dec }; // or throw error
    
    return Math.ceil(int / rule.integer) * rule.integer
         + decRule.value / 100;
  };
}

const data = {
  "us": [
    {
      "start": 100,
      "end": 200,
      "integer": 5,
      "decimals": [
        {"start":  0, "end": 50, "value": 50},
        {"start": 50, "end": 90, "value": 90},
      ]
    }
  ]
};

const round = createRound(data.us);

console.log(round(101.10)); // 105.50
console.log(round(126.40)); // 130.50
console.log(round(144.60)); // 145.90
console.log(round(156.60)); // 160.90

console.log(round( 12.34)); //  12.34 (no rule)
console.log(round(156.95)); // 160.95 (no decimal rule)