import { bf, BigFraction, Complex, BigFloat} from "./bf.js";
/**
* Scalar Wrapper Class.
* Manages dispatching and type promotion between BigFraction, BigFloat, and Complex.
*
* Hierarchy:
* Level 0: BigFraction (Rational)
* Level 1: BigFloat (Real/Float)
* Level 2: Complex (Complex)
*
* @property {BigFraction|BigFloat|Complex} value - The underlying numeric value.
* @property {number} level - The type promotion level (0, 1, or 2).
*/
export class Scalar {
/**
* @param {number|bigint|string|BigFraction|BigFloat|Complex|Scalar} v
*/
constructor(v) {
if (v instanceof Scalar) {
this.value = v.value;
this.level = v.level;
return;
}
const type = typeof v;
if (type === 'number') {
if (Number.isInteger(v)) {
this.value = new BigFraction(v);
this.level = 0;
} else {
this.value = bf(v); // bf is assumed to be the BigFloat factory
this.level = 1;
}
}
else if (type === 'bigint') {
this.value = new BigFraction(v);
this.level = 0;
}
else if (type === 'string') {
const parsed = Scalar.fromString(v);
this.value = parsed.value;
this.level = parsed.level;
}
else {
// v is already a math object (Complex, BigFloat, or BigFraction)
this.value = v;
this.level = Scalar.getLevel(v);
}
}
/**
* Determine the level of a raw math object.
* @param {any} v The value to check.
* @returns {number} The promotion level.
*/
static getLevel(v) {
if (v instanceof Complex) return 2;
if (v instanceof BigFloat) return 1;
if (v instanceof BigFraction) return 0;
return 1;
}
/**
* Promotes a raw value to the target level.
* @param {any} v The value to promote.
* @param {number} targetLevel The target promotion level.
* @returns {any} The promoted value.
*/
static promote(v, targetLevel) {
const currentLevel = Scalar.getLevel(v);
if (currentLevel >= targetLevel) return v;
if (targetLevel === 1) {
// Level 0 -> 1: BigFraction to BigFloat
return v.toBigFloat ? v.toBigFloat() : bf(v.toString());
}
if (targetLevel === 2) {
// Level 0/1 -> 2: Scalar to Complex
const realPart = Scalar.promote(v, 1);
return new Complex(realPart, 0);
}
return v;
}
// --- Arithmetic Operators ---
/**
* Internal dispatcher for binary operations.
* @private
* @param {Scalar|any} a - The first operand.
* @param {Scalar|any} b - The second operand.
* @param {string} opName - The name of the operation.
* @returns {Scalar} The result of the operation.
*/
static _binaryOp(a, b, opName) {
const va = a instanceof Scalar ? a.value : a;
const vb = b instanceof Scalar ? b.value : b;
const levelA = Scalar.getLevel(va);
const levelB = Scalar.getLevel(vb);
let na = va;
let nb = vb;
for(let targetLevel = Math.max(levelA, levelB);targetLevel<=3;targetLevel++){
na = Scalar.promote(na, targetLevel);
nb = Scalar.promote(nb, targetLevel);
let v = na[opName](nb);
if(v!==undefined){
return new Scalar(v);
}
}
throw new Error(`${opName} failed for ${a.toString()}, ${b.toString()}`);
}
/** @param {Scalar|any} other @returns {Scalar} */
add(other) { return Scalar._binaryOp(this, other, 'add'); }
/** @param {Scalar|any} other @returns {Scalar} */
sub(other) { return Scalar._binaryOp(this, other, 'sub'); }
/** @param {Scalar|any} other @returns {Scalar} */
mul(other) { return Scalar._binaryOp(this, other, 'mul'); }
/** @param {Scalar|any} other @returns {Scalar} */
div(other) { return Scalar._binaryOp(this, other, 'div'); }
/** @param {Scalar|any} other @returns {Scalar} */
pow(other) { return Scalar._binaryOp(this, other, 'pow'); }
// Alias for operator styles
/** @param {Scalar|any} b @returns {Scalar} */
operatorAdd(b) { return this.add(b); }
/** @param {Scalar|any} b @returns {Scalar} */
operatorSub(b) { return this.sub(b); }
/** @param {Scalar|any} b @returns {Scalar} */
operatorMul(b) { return this.mul(b); }
/** @param {Scalar|any} b @returns {Scalar} */
operatorDiv(b) { return this.div(b); }
/** @param {Scalar|any} b @returns {Scalar} */
operatorPow(b) { return this.pow(b); }
/** @returns {Scalar} */
operatorNeg() { return this.neg(); }
/** @returns {Scalar} */
neg() { return new Scalar(this.value.neg()); }
/** @returns {boolean} */
isZero() {return this.value.isZero();}
/** @returns {boolean} */
isAlmostZero(){return this.value.isAlmostZero();};
/** @returns {Scalar} */
abs() { return new Scalar(this.value.abs()); }
/**
* Internal dispatcher for functions that might return undefined for BigFraction.
* @private
* @param {string} opName The name of the operation.
* @returns {Scalar}
*/
_unary(opName) {
if (this.level === 0) {
const res = this.value[opName]();
if (res !== undefined) return new Scalar(res);
// Result is irrational, promote to BigFloat and retry
const promoted = this.value.toBigFloat();
return new Scalar(promoted[opName]());
}
return new Scalar(this.value[opName]());
}
/** @returns {Scalar} */
exp() { return this._unary('exp'); }
/** @returns {Scalar} */
log() { return this._unary('log'); }
/** @returns {Scalar} */
sin() { return this._unary('sin'); }
/** @returns {Scalar} */
cos() { return this._unary('cos'); }
/** @returns {Scalar} */
tan() { return this._unary('tan'); }
/** @returns {Scalar} */
asin() { return this._unary('asin'); }
/** @returns {Scalar} */
acos() { return this._unary('acos'); }
/** @returns {Scalar} */
atan() { return this._unary('atan'); }
/** @returns {Scalar} */
sinh() { return this._unary('sinh'); }
/** @returns {Scalar} */
cosh() { return this._unary('cosh'); }
/** @returns {Scalar} */
tanh() { return this._unary('tanh'); }
/** @returns {Scalar} */
asinh() { return this._unary('asinh'); }
/** @returns {Scalar} */
acosh() { return this._unary('acosh'); }
/** @returns {Scalar} */
atanh() { return this._unary('atanh'); }
/** @returns {Scalar} */
sqrt() { return this._unary('sqrt'); }
// --- Comparison & Utilities ---
/**
* Compares this scalar with another value.
* @param {Scalar|any} other
* @returns {number} -1 if this < other, 0 if this === other, 1 if this > other.
*/
cmp(other) {
const vb = (other instanceof Scalar) ? other.value : other;
const targetLevel = Math.max(this.level, Scalar.getLevel(vb));
if (targetLevel === 2) throw new Error("Complex numbers are not ordered.");
return Scalar.promote(this.value, targetLevel).cmp(Scalar.promote(vb, targetLevel));
}
/**
* Checks for equality with another value.
* @param {Scalar|any} other
* @returns {boolean}
*/
equals(other) {
const vb = (other instanceof Scalar) ? other.value : other;
const targetLevel = Math.max(this.level, Scalar.getLevel(vb));
return Scalar.promote(this.value, targetLevel).equals(Scalar.promote(vb, targetLevel));
}
/**
* Converts the scalar to a string.
* @param {number} [radix=10]
* @param {number} [precision=-1]
* @param {boolean} [pretty=false] pretty print
* @returns {string}
*/
toString(radix = 10, precision = -1, pretty=false) {
return this.value.toString(radix, precision, pretty);
}
/**
* Parses string and determines correct type/level.
* @param {string} str
* @returns {Scalar}
*/
static fromString(str) {
const s = str.trim();
// 1. Complex check
if (s.includes('i')) {
return new Scalar(Complex.fromString(s));
}
// 2. Fraction check
if (s.includes('/')) {
return new Scalar(BigFraction.fromString(s));
}
// 3. Float or Integer check
if (s.includes('.') || s.toLowerCase().includes('e')) {
let nv=parseFloat(s);
if(Number.isInteger(nv)){
return new Scalar(BigFraction.fromString(s));
}
return new Scalar(BigFloat.fromString(s));
}
// 4. Default to Fraction if it's a clean integer string
try {
return new Scalar(new BigFraction(s));
} catch(e) {
return new Scalar(bf(s));
}
}
}
/**
* Factory function for creating Scalar instances.
* @param {number|bigint|string|BigFraction|BigFloat|Complex|Scalar} s
* @returns {Scalar}
*/
export function scalar(s){
if(s instanceof Scalar){
return s;
}
return new Scalar(s);
}