Skip to content

Instantly share code, notes, and snippets.

@nexpr
Created August 18, 2023 12:33
Show Gist options
  • Save nexpr/deff34cd914f2827325feb1359a26523 to your computer and use it in GitHub Desktop.
Save nexpr/deff34cd914f2827325feb1359a26523 to your computer and use it in GitHub Desktop.
ceil, round, floor 関数
export const ceil = (n, d) => Math.ceil(n * (10 ** d)) / (10 ** d)
export const round = (n, d) => Math.round(n * (10 ** d)) / (10 ** d)
export const floor = (n, d) => Math.floor(n * (10 ** d)) / (10 ** d)
const fix = n => +n.toPrecision(14)
export const ceil = (n, d) => fix(Math.ceil(fix(n * (10 ** d))) / (10 ** d))
export const round = (n, d) => fix(Math.round(fix(n * (10 ** d))) / (10 ** d))
export const floor = (n, d) => fix(Math.floor(fix(n * (10 ** d))) / (10 ** d))
const common = (numstr, digit, action) => {
if (digit < 0) throw new Error("digit should be >= 0")
digit = ~~digit
numstr = String(numstr)
if (!numstr.match(/^\d+(\.\d+)?$/)) throw new Error("numstr is invalid format")
let [left, right = ""] = numstr.split(".")
right = right.padEnd(digit, "0")
const dropped = right.slice(digit)
right = right.slice(0, digit)
const bdigit = 10n ** BigInt(digit)
let b = BigInt(left) * bdigit + BigInt(right)
if (action === "ceil") {
if (+dropped) {
b++
}
} else if (action === "round") {
if (+dropped[0] >= 5) {
b++
}
}
const result_left = String(b / bdigit)
const result_right = String(b % bdigit).padStart(digit, "0").slice(0, digit)
return result_left + (result_right ? "." + result_right : "")
}
export const ceil = (numstr, digit) => common(numstr, digit, "ceil")
export const round = (numstr, digit) => common(numstr, digit, "round")
export const floor = (numstr, digit) => common(numstr, digit, "floor")
export const trunc = (numstr, digit) => common(numstr, digit, "trunc")

ceil, round, floor 関数

JavaScript の Math.ceil や Math.round などは小数点以下の任意の桁数を指定できない

簡単そうな方法は小数点以下 3 桁なら 1000 倍して関数通して 1000 で割る (1)

0.1234 なら 123.4 にして Math.ceil で 124 にしてから 0.124 に戻す

誤差があるのでこういう場合が出る

floor(0.29, 2)
// 0.28
0.29 * 100
// 28.999999999999996

1000 倍と 1000 で割った後に誤差補正のために文字列を経由する (2)

floor(0.29, 2)
// 0.29

桁が大きくなると補正の限界が来る

round(0.0499999999999999, 1)
// 0.1

number 型 (64bit float) の限界もある

0.9999999999999999
// 0.9999999999999999
0.99999999999999999
// 1

文字列として処理する (3)

文字列のままだと繰り上げ時の処理が扱いづらいの BigInt に任せる

実装が複雑になるのでマイナスの場合は対応してない

round("0.123456789012345678905", 20)
// '0.12345678901234567891'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment