かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

JavaScript 5刻みの数値に変換したい

与えられた数値を 0, 5, 10, 15, … と 5刻みに丸めたい

結論

const get5RoundNumber = (n: number) => Math.round(n * 2 / 10) * 10 / 2;
  1. 5 刻みの数値を得るには 10 刻み 0, 10, 20, 30, ... を 2 で割れば 0, 5, 10, 15, … ととなる
  2. 0, 10, 20, 30,… という数字を得るには、四捨五入で 0, 1, 2, 3,... を作り 10 倍すれば良い
  3. 最終的に 2 で割るので、もとの数値を 2倍しておく
  4. 1の位で四捨五入するには 1の位を小数点第一位にするために、10で割れば良い

文章で書いてもわかりにくいので表にする

5 刻みに丸める

n * 2 / 10 Math.round * 10 / 2 (Result)
0 0.2 0 0 0
1 0.2 0 0 0
2 0.4 0 0 0
3 0.6 1 10 5
4 0.8 1 10 5
5 10 1 10 5
6 1.2 1 10 5
7 1.4 1 10 5
8 1.6 2 20 10
9 1.8 2 20 10
10 2.0 2 20 10

0.5 刻みに丸める

小数点第二位を四捨五入して 0.5 刻みに丸める場合は 10 で割らなければ良い

n * 2 Math.round / 2 (Result)
0.0 0.2 0 0
0.1 0.2 0 0
0.2 0.4 0 0
0.3 0.6 1 0.5
0.4 0.8 1 0.5
0.5 10 1 0.5
0.6 1.2 1 0.5
0.7 1.4 1 0.5
0.8 1.6 2 1.0
0.9 1.8 2 1.0
1.0 2.0 2 1.0

桁の調整

  • 50 刻みなら 100 で割って Math.round し、100 を掛ける。
  • 5 刻みなら 10 で割って Math.round し、10 を掛ける。
  • 0.5 刻みなら 1 で割って Math.round し、10 を掛ける。
  • 0.05 刻みなら 10 を掛けて Math.round し、10 で割る。

桁数 * 10 をした値が、1 以上なら 割って Math.roundし、掛ける、1以下なら 掛けて Math.round し、で割ればよい。

const getAdjustDigits = (n: number) => {
  const digits = Math.floor(n);
  if (digits > 0) {
    return Math.pow(10, digits);
  } else if (digits < 0) {
    // 桁数が整数の場合は正、少数の場合は負の数にして返す
    return -Math.pow(10, Math.abs(digits) - 1);
  } else {
    return 0;
  }
}

const get5RoundNumber = (n: number, digits: number = 1) => {
  // 桁数が 0 のとき小数点第一位まで表示する
  if (digits === 0) {
    return Math.round(n * 2) / 2;
  }

  const x = getAdjustDigits(digits);
  if (x > 0) {
    return Math.round(n * 2 / x) * x / 2;
  }
  
  const j = Math.abs(x);
  return Math.round(n * 2 * j) / j / 2;
}

こんな感じで 5 刻みの数値に変換できそう
5 になりやすいので微妙に調整したほうが良い気もするが、ざっくり要件が満たせてると思う

有効桁数のひとつ下の桁数を四捨五入してから 5刻みに変換する

5桁で丸める前に数字を有効桁数の一つ下で四捨五入すれば良い

const getAdjustDigits = (n: number) => {
  const digits = Math.floor(n);
  if (digits > 0) {
    return Math.pow(10, digits);
  } else if (digits < 0) {
    // 桁数が整数の場合は正、少数の場合は負の数にして返す
    return -Math.pow(10, Math.abs(digits) - 1);
  } else {
    return 0;
  }
}

// 有効桁数の1つ下の桁で四捨五入した数値を返す
const getRoundedNumber = (n: number, digits: number, isInteger: boolean = true) => {
  const d = (isInteger && digits > 1) ? digits / 10 : digits * 10;
  
  if (isInteger) {
    return Math.round(n / d) * d;
  }
  
  return Math.round(n * d) / d;
}

const get5RoundNumber = (n: number, digits: number = 1) => {
  // 桁数が 0 のとき小数点第一位まで表示する
  if (digits === 0) {
    return Math.round(n * 2) / 2;
  }
  
  const x = getAdjustDigits(digits);
  const j = Math.abs(x);
  const roundN = getRoundedNumber(n, j, x > 0);

  if (x > 0) {
    return Math.round(roundN * 2 / x) * x / 2;
  }
  
  return Math.round(roundN * 2 * j) / j / 2;
}

Sample

See the Pen 5 round number by KIKIKI (@kikiki_kiki) on CodePen.

おわり