@0xtorch/big-decimal
Version:
An arbitrary precision Decimal type for TypeScript that extends BigInt.
111 lines (100 loc) • 3.04 kB
text/typescript
import type { BigDecimal } from './type'
export const createBigDecimal = (
value: string | number | bigint,
decimals?: number,
): BigDecimal => {
if (typeof value === 'string') {
return createBigDecimalFromString(value, decimals)
}
if (typeof value === 'number') {
return createBigDecimalFromString(value.toString(), decimals)
}
return createBigDecimalFromBigint(value, decimals)
}
const createBigDecimalFromString = (
value: string,
decimals?: number,
): BigDecimal => {
// e で分割
const splitedValue = value.replaceAll(' ', '').split(/[eE]/)
const baseValue = splitedValue[0]
const exponent: string | undefined = splitedValue[1]
// マイナスかどうか
const isNegative = baseValue.startsWith('-')
const absText = isNegative ? baseValue.slice(1) : baseValue
// '+' ',' '_' を除外する
const numberText = absText
.replaceAll('+', '')
.replaceAll(',', '')
.replaceAll('_', '')
// . で文字列分割
// それぞれの block が数字のみで構成されていなかったらエラー
const [intText, decimalText] = numberText.split('.')
const numberRegExp = /^\d+$/
if (
!numberRegExp.test(intText) ||
(decimalText !== undefined &&
decimalText.length > 0 &&
!numberRegExp.test(decimalText))
) {
throw new Error(`Invalid decimal format value: ${baseValue}`)
}
// decimal 末尾の 0 は削除 (正規表現で良い感じに)
const formattedDecimalText =
decimalText !== undefined && decimalText.length > 0
? decimalText.replace(/0+$/, '')
: ''
const formattedDecimals = formattedDecimalText.length
// decimals の末尾を調整
const adjustedDecimalText = adjustDecimalText(
decimals,
formattedDecimalText,
formattedDecimals,
)
const valueBn = BigInt(
`${isNegative ? '-' : ''}${intText}${adjustedDecimalText}`,
)
const baseDecimals = decimals === undefined ? formattedDecimals : decimals
// 指数がない場合はそのまま返す
if (exponent === undefined) {
return {
value: valueBn,
decimals: baseDecimals,
}
}
// 指数がある場合は指数を考慮して返す
const exponentNumber = Number(exponent)
const decimalsWithExponent = baseDecimals - exponentNumber
return decimalsWithExponent < 0
? {
value: valueBn * 10n ** BigInt(-decimalsWithExponent),
decimals: 0,
}
: {
value: valueBn,
decimals: decimalsWithExponent,
}
}
const adjustDecimalText = (
decimals: number | undefined,
formattedDecimalText: string,
formattedDecimals: number,
) => {
if (decimals === undefined) {
return formattedDecimalText
}
if (decimals > formattedDecimals) {
return formattedDecimalText.padEnd(decimals, '0')
}
if (decimals < formattedDecimals) {
return formattedDecimalText.slice(0, decimals)
}
return formattedDecimalText
}
const createBigDecimalFromBigint = (
value: bigint,
decimals?: number,
): BigDecimal => ({
value,
decimals: decimals ?? 0,
})