UNPKG

@steve02081504/bigfloat

Version:

A simple JavaScript implementation of infinite precision arithmetic.

453 lines (427 loc) 13.1 kB
// bigfloat以无限精度运算的简易js实现 // 逻辑来自elc class ubigfloat { //分子分母 numerator = 0n denominator = 1n gc() { const gcd = (a, b) => a ? gcd(b % a, a) : b const common = gcd(this.numerator, this.denominator) this.numerator /= common this.denominator /= common return this } static fromPair(numerator, denominator) { const ubf = new ubigfloat() ubf.numerator = BigInt(numerator) ubf.denominator = BigInt(denominator) return ubf } constructor(value) { // 是否小数? if (value instanceof ubigfloat) return value else if (Object(value) instanceof BigInt) this.numerator = value else if (Object(value) instanceof Number && Number.isInteger(value)) this.numerator = BigInt(value) else if (value) { const string = String(value) return ubigfloat.fromString(string) } } add(other) { return ubigfloat.fromPair( this.numerator * other.denominator + other.numerator * this.denominator, this.denominator * other.denominator ) } sub(other) { return ubigfloat.fromPair( this.numerator * other.denominator - other.numerator * this.denominator, this.denominator * other.denominator ) } mul(other) { return ubigfloat.fromPair( this.numerator * other.numerator, this.denominator * other.denominator ) } div(other) { return ubigfloat.fromPair( this.numerator * other.denominator, this.denominator * other.numerator ) } mod(other) { if (this.isInf()) return other if (!other.numerator) return new ubigfloat() return ubigfloat.fromPair( this.numerator * other.denominator % (this.denominator * other.numerator), this.denominator * other.denominator ) } pow(other) { if (other.isInf()) return other const pow = other.floor() return ubigfloat.fromPair( this.numerator ** pow, this.denominator ** pow ) } isInf() { return !this.denominator } equals(other) { if (this.isInf() != other.isInf()) return false return this.numerator * other.denominator === other.numerator * this.denominator } lessThan(other) { if (this.isInf() != other.isInf()) return other.isInf() return this.numerator * other.denominator < other.numerator * this.denominator } greaterThan(other) { if (this.isInf() != other.isInf()) return this.isInf() return this.numerator * other.denominator > other.numerator * this.denominator } compare(other) { return this.greaterThan(other) ? 1 : this.lessThan(other) ? -1 : 0 } floor() { if (this.isInf()) return this return this.numerator / this.denominator } toString() { if (this.denominator === 1n) return this.numerator.toString() if (this.denominator === 0n) return '∞' const integer = this.numerator / this.denominator let decimal = this.numerator - integer * this.denominator let result = integer.toString() if (decimal) { result += '.' const forever_loop_set = new Set() while (decimal) { decimal *= 10n const char = (decimal / this.denominator).toString() decimal %= this.denominator if (forever_loop_set.has(decimal)) { // add [ and ] to looping part const loop_part = result.slice(-forever_loop_set.size) const loop_before = result.slice(0, result.length - loop_part.length) result = loop_before + '[' + loop_part + ']' break } forever_loop_set.add(decimal) result += char } } return result } static fromString(string) { if (string === '∞') return ubigfloat.fromPair(1n, 0n) // handle [ and ] if (string.includes('[')) { const loop_part = string.slice(string.indexOf('[') + 1, string.indexOf(']')) const loop_before = string.slice(0, string.indexOf('[')) const times = 7 const loopfewtimes = loop_part.repeat(times) let missing_numerator = ubigfloat.fromPair(BigInt('1' + '0'.repeat(times * loop_part.length)), BigInt(loopfewtimes) - BigInt(loop_part)) const scale = loop_before.split('.')[1]?.length || 0 missing_numerator = ubigfloat.fromPair(1n, missing_numerator.floor() * 10n ** BigInt(scale)) const basenum = ubigfloat.fromString(loop_before) return basenum.add(missing_numerator) } else { const result = new ubigfloat() const point_index = string.indexOf('.') if (point_index === -1) { result.numerator = BigInt(string) return result } const before_point = string.slice(0, point_index) const after_point = string.slice(point_index + 1) result.denominator = 10n ** BigInt(after_point.length) result.numerator = BigInt(before_point) * result.denominator + BigInt(after_point) return result } } } class bigfloat { basenum = new ubigfloat() sign = false constructor(value) { if (value instanceof bigfloat) return value else if (value) { let string = String(value) if (string.startsWith('-')) { this.sign = true string = string.slice(1) } this.basenum = ubigfloat.fromString(string) } } toString() { return (this.sign ? '-' : '') + this.basenum.toString() } static fromString(string) { return new bigfloat(string) } static fromNumAndSign(sign, ufloat) { const result = new bigfloat() result.sign = sign result.basenum = ufloat return result } static fromPairAndSign(sign, numerator, denominator) { return bigfloat.fromNumAndSign(sign, ubigfloat.fromPair(numerator, denominator)) } abs() { return bigfloat.fromNumAndSign(false, this.basenum) } neg() { return bigfloat.fromNumAndSign(!this.sign, this.basenum) } add(other) { other = new bigfloat(other) if (this.sign === other.sign) return bigfloat.fromNumAndSign(this.sign, this.basenum.add(other.basenum)) else if (this.abs().greaterThan(other.abs())) return bigfloat.fromNumAndSign(this.sign, this.basenum.sub(other.basenum)) else return bigfloat.fromNumAndSign(other.sign, other.basenum.sub(this.basenum)) } sub(other) { return this.add(other.neg()) } mul(other) { other = new bigfloat(other) return bigfloat.fromNumAndSign(this.sign !== other.sign, this.basenum.mul(other.basenum)) } div(other) { other = new bigfloat(other) return bigfloat.fromNumAndSign(this.sign !== other.sign, this.basenum.div(other.basenum)) } mod(other) { other = new bigfloat(other) return bigfloat.fromNumAndSign(this.sign, this.basenum.mod(other.basenum)) } pow(other) { other = new bigfloat(other) return bigfloat.fromNumAndSign(this.sign, this.basenum.pow(other.basenum)) } isInf() { return this.basenum.isInf() } equals(other) { other = new bigfloat(other) if (this.basenum.equals(other.basenum)) if (!this.basenum.numerator) return true else return this.sign === other.sign return false } lessThan(other) { other = new bigfloat(other) if (this.sign !== other.sign) return this.sign else if (this.sign) // 都是负数 return this.basenum.greaterThan(other.basenum) // 负数绝对值大的反而小 else return this.basenum.lessThan(other.basenum) } greaterThan(other) { other = new bigfloat(other) if (this.sign !== other.sign) return !this.sign else if (this.sign) return this.basenum.lessThan(other.basenum) // 负数绝对值小的反而大 else return this.basenum.greaterThan(other.basenum) } compare(other) { other = new bigfloat(other) if (this.sign !== other.sign) return this.sign ? -1 : 1 else return this.basenum.compare(other.basenum) } floor() { return bigfloat.fromNumAndSign(this.sign, new ubigfloat(this.basenum.floor())) } toBoolean() { return Boolean(this.basenum.numerator) } static eval(string) { // 去除所有空格 string = string.replace(/\s+/g, '') // 校验表达式合法性 if (!/^[\d!%&()*+./<=>|\-]+$/.test(string)) throw new Error(`Invalid characters in expression: ${string}`) // 定义运算符优先级和关联性 const precedence = { '**': { prec: 4, assoc: 'right' }, '~': { prec: 5, assoc: 'right' }, // 一元负号 '*': { prec: 3, assoc: 'left' }, '/': { prec: 3, assoc: 'left' }, '%': { prec: 3, assoc: 'left' }, '+': { prec: 2, assoc: 'left' }, '-': { prec: 2, assoc: 'left' }, '<': { prec: 1, assoc: 'left' }, '>': { prec: 1, assoc: 'left' }, '<=': { prec: 1, assoc: 'left' }, '>=': { prec: 1, assoc: 'left' }, '==': { prec: 1, assoc: 'left' }, '!=': { prec: 1, assoc: 'left' }, '&&': { prec: 0, assoc: 'left' }, '||': { prec: 0, assoc: 'left' }, '!': { prec: 5, assoc: 'right' }, // 与一元负号相同 } // 将中缀表达式转换为后缀表达式 (Shunting-Yard 算法) function toPostfix(tokens) { const outputQueue = [] const operatorStack = [] for (let i = 0; i < tokens.length; i++) { const token = tokens[i] if (token.match(/[\d.]+/)) // 数字直接进入输出队列 outputQueue.push(token) else if (token in precedence) // 处理一元负号 if (token === '-' && (i === 0 || tokens[i - 1] in precedence || tokens[i - 1] === '(')) operatorStack.push('~') // 用 "~" 表示一元负号 else { while ( operatorStack.length > 0 && // 确保 operatorStack 不为空 operatorStack[operatorStack.length - 1] !== '(' && (precedence[token].prec < precedence[operatorStack[operatorStack.length - 1]].prec || (precedence[token].prec === precedence[operatorStack[operatorStack.length - 1]].prec && precedence[token].assoc === 'left')) ) outputQueue.push(operatorStack.pop()) operatorStack.push(token) } else if (token === '(') // 左括号入栈 operatorStack.push(token) else if (token === ')') { // 右括号,弹出运算符直到遇到左括号 while (operatorStack.length > 0 && operatorStack[operatorStack.length - 1] !== '(') outputQueue.push(operatorStack.pop()) if (operatorStack.length === 0) throw new Error('Mismatched parentheses') operatorStack.pop() // 弹出左括号 } else throw new Error(`Invalid token: ${token}`) } // 将剩余的运算符弹出 while (operatorStack.length > 0) { if (operatorStack[operatorStack.length - 1] === '(') throw new Error('Mismatched parentheses') outputQueue.push(operatorStack.pop()) } return outputQueue } // 计算后缀表达式 function evaluatePostfix(postfix) { const stack = [] for (const token of postfix) if (token.match(/[\d.]+/)) // 数字直接入栈 stack.push(new bigfloat(token)) else if (token in precedence || token === '~') // 运算符 if (token === '!') { const operand = stack.pop() stack.push(new bigfloat(!operand.toBoolean())) } else if (token === '~') { const operand = stack.pop() stack.push(operand.neg()) } else { const right = stack.pop() const left = stack.pop() switch (token) { case '+': stack.push(left.add(right)) break case '-': stack.push(left.sub(right)) break case '*': stack.push(left.mul(right)) break case '/': stack.push(left.div(right)) break case '%': stack.push(left.mod(right)) break case '**': stack.push(left.pow(right)) break case '==': stack.push(new bigfloat(left.equals(right))) break case '<': stack.push(new bigfloat(left.lessThan(right))) break case '>': stack.push(new bigfloat(left.greaterThan(right))) break case '<=': stack.push(new bigfloat(!left.greaterThan(right))) break case '>=': stack.push(new bigfloat(!left.lessThan(right))) break case '!=': stack.push(new bigfloat(!left.equals(right))) break case '&&': stack.push(new bigfloat(left.toBoolean() && right.toBoolean())) break case '||': stack.push(new bigfloat(left.toBoolean() || right.toBoolean())) break default: throw new Error(`Invalid operator: ${token}`) } } if (stack.length !== 1) throw new Error(`Invalid expression: ${string}`) return stack[0] } // 分词 const tokens = string.match(/(\d+(\.\d+)?(\[\d+])?|\*\*|[%*+/\-]|&&|\|\||<=|>=|==|!=|!|\(|\))/g) // 将中缀表达式转换为后缀表达式 const postfix = toPostfix(tokens) // 计算后缀表达式并返回结果 return evaluatePostfix(postfix) } static evalFromStrings(string) { const exprs = string.match(/[\d!%()*+/<=>[\]\-]+/g) /** @type {Record<string, bigfloat>} */ const result = {} for (const expr of exprs) try { if (expr.match(/^[\d.]*$/)) continue // 跳过纯数字 else if (!expr.match(/\d/)) continue // 跳过纯运算符 result[expr] = bigfloat.eval(expr) } catch (e) { } return result } } /** * @type {bigfloat & { * (value: string | number | undefined) => bigfloat * eval(value: string | number | undefined): bigfloat * evalFromStrings(string: string): Record<string, bigfloat> * }} */ const bigfloatProxy = new Proxy(bigfloat, { apply(target, thisArg, args) { return new bigfloat(args[0]) } }) export { bigfloatProxy as bigfloat }