Category talk:Wren-big: Difference between revisions

Content added Content deleted
(→‎Arbitrary precision arithmetic: Extended preamble to include the new BigRat class.)
(→‎Source code: Added BigRat and BigRats classes.)
Line 154: Line 154:
static trim_(a) {
static trim_(a) {
var i = a.count - 1
var i = a.count - 1
while(i >= 0 && a[i] == 0) {
while (i >= 0 && a[i] == 0) {
a.removeAt(i)
a.removeAt(i)
i = i - 1
i = i - 1
Line 443: Line 443:
borrow = borrow + carry
borrow = borrow + carry
}
}
result[shift] = quotDigit
result[shift] = quotDigit
shift = shift - 1
shift = shift - 1
}
}
Line 937: Line 937:
+ (n) {
+ (n) {
if (!(n is BigInt)) n = BigInt.new(n)
if (!(n is BigInt)) n = BigInt.new(n)
if (n.isZero) return this
if (this.isSmall) return smallAdd_(n)
if (this.isSmall) return smallAdd_(n)
if (_signed != n.signed_) return this - (-n)
if (_signed != n.signed_) return this - (-n)
Line 959: Line 960:
- (n) {
- (n) {
if (!(n is BigInt)) n = BigInt.new(n)
if (!(n is BigInt)) n = BigInt.new(n)
if (n.isZero) return this
if (this.isSmall) return smallSubtract_(n)
if (this.isSmall) return smallSubtract_(n)
if (_signed != n.signed_) return this + (-n)
if (_signed != n.signed_) return this + (-n)
Line 1,279: Line 1,281:
static sum(a) { a.reduce(BigInt.zero) { |acc, x| acc + x } }
static sum(a) { a.reduce(BigInt.zero) { |acc, x| acc + x } }
static prod(a) { a.reduce(BigInt.one) { |acc, x| acc * x } }
static prod(a) { a.reduce(BigInt.one) { |acc, x| acc * x } }
static max(a) { a.reduce { |acc, x| (x > acc) ? x : acc } }
static min(a) { a.reduce { |acc, x| (x < acc) ? x : acc } }
}

/* BigRat represents a rational number as a BigInt numerator and (non-zero) denominator
expressed in their lowest terms. BigRat objects are immutable.
*/
class BigRat is Comparable {
// Private helper function to check that 'o' is a suitable type and throw an error otherwise.
// Rational numbers, numbers and numeric strings are returned as BigRats.
static check_(o) {
if (o is BigRat) return o
if (o is BigInt) return BigRat.new(o, BigInt.one)
if (o.type.toString == "Rat") return BigRat.fromRat(o)
if (o is Num) return BigRat.fromFloat(o)
if (o is String) return (o.contains("_") && o.contains("/")) ? fromMixedString(o) :
o.contains("/") ? fromRationalString(o) : fromDecimal(o)
Fiber.abort("Argument must either be a rational number, a number or a numeric string.")
}

// Constants.
static minusOne { BigRat.new(BigInt.minusOne, BigInt.one) }
static zero { BigRat.new(BigInt.zero, BigInt.one) }
static one { BigRat.new(BigInt.one, BigInt.one) }
static two { BigRat.new(BigInt.two, BigInt.one) }
static ten { BigRat.new(BigInt.ten, BigInt.one) }
static half { BigRat.new(BigInt.one, BigInt.two) }
static tenth { BigRat.new(BigInt.one, BigInt.ten) }

// Constructs a new BigRat object by passing it a numerator and a denominator.
// These must either be BigInts or Nums/Strings which are capable of creating one
// when passed to the BigInt.new constructor.
construct new(n, d) {
n = (n is BigInt) ? n.copy() : BigInt.new(n)
d = (d is BigInt) ? d.copy() : BigInt.new(d)
if (d == BigInt.zero) Fiber.abort("Denominator must be a non-zero integer.")
if (n == BigInt.zero) {
d = BigInt.one
} else if (d < BigInt.zero) {
n = -n
d = -d
}
var g = BigInt.gcd(n, d).abs
if (g > BigInt.one) {
n = n / g
d = d / g
}
_n = n
_d = d
}

// Convenience method which constructs a new BigRat object by passing it just a numerator.
static new(n) { BigRat.new(n, BigInt.one) }

// Constructs a BigRat object from a Rat object. To use this method the Rat class needs
// to be imported from the Wren-rat module as, to minimize dependencies,
// this module does not do so.
static fromRat(r) {
if (r.type.toString != "Rat") Fiber.abort("Argument must be a rational number.")
return BigRat.new(r.num, r.den)
}

// Constructs a BigRat object from a string of the form "n/d".
// Improper fractions are allowed.
static fromRationalString(s) {
var nd = s.split("/")
if (nd.count != 2) Fiber.abort("Argument is not a suitable string.")
var n = BigInt.new(nd[0])
var d = BigInt.new(nd[1])
return BigRat.new(n, d)
}

// Constructs a BigRat object from a string of the form "i_n/d" where 'i' is an integer.
// Improper and negative fractional parts are allowed.
static fromMixedString(s) {
var ind = s.split("_")
if (ind.count != 2) Fiber.abort("Argument is not a suitable string.")
var nd = fromRationalString(ind[1])
var i = BigRat.new(ind[0])
var neg = i.isNegative || (i.isZero && ind[0][0] == "-")
return neg ? i - nd : i + nd
}

// Constructs a BigRat object from a decimal numeric string or value.
static fromDecimal(s) {
if (!(s is String)) s = s.toString
if (s == "") Fiber.abort("Argument cannot be an empty string.")
s = s.trim().trim("0")
if (s == "") return BigRat.zero
if (s.startsWith(".")) s = "0" + s
if (!s.contains(".")) return BigRat.new(s)
if (s.endsWith(".")) return BigRat.new(s[0..-2])
var splits = s.split(".")
if (splits.count != 2) Fiber.abort("Argument is not a decimal.")
var num = splits[0]
var isNegative = false
if (num.startsWith("-")) {
isNegative = true
num = num[1..-1]
} else if (num.startsWith("+")) {
num = num[1..-1]
}
var den = splits[1]
if (!num.all { |c| "0123456789".contains(c) } || !den.all { |c| "0123456789".contains(c) }) {
Fiber.abort("Argument is not a decimal.")
}
num = BigInt.new(num + den)
if (isNegative) num = -num
den = BigInt.ten.pow(den.count)
return BigRat.new(num, den)
}

// Constructs a rational number from a floating point number provided the latter is an integer
// or has a decimal string representation.
static fromFloat(n) {
if (!(n is Num)) Fiber.abort("Argument must be a number.")
if (n.isInteger) return BigRat.new(n, BigInt.one)
return fromDecimal(n)
}

// Returns the greater of two BigRat objects.
static max(r1, r2) { (r1 < r2) ? r2 : r1 }

// Returns the smaller of two BigRat objects.
static min(r1, r2) { (r1 < r2) ? r1 : r2 }

// Determines whether a BigRat object is always shown as such or, if integral, as an integer.
static showAsInt { __showAsInt }
static showAsInt=(b) { __showAsInt = b }

// Basic properties.
num { _n } // numerator
den { _d } // denominator
ratio { [_n, _d] } // a two element list of the above
isInteger { _d == 1 } // checks if integral or not
isPositive { _n > BigInt.zero } // checks if positive
isNegative { _n < BigInt.zero } // checks if negative
isUnit { _n.abs == BigInt.one } // checks if plus or minus one
isZero { _n == BigInt.zero } // checks if zero

// Rounding methods (similar to those in Num class).
ceil { // higher integer
if (isInteger) return this
var div = _n/_d
if (!this.isNegative) div = div.inc
return BigRat.new(div, BigInt.one)
}

floor { // lower integer
if (isInteger) return this
var div = _n/_d
if (this.isNegative) div = div.dec
return BigRat.new(div, BigInt.one)
}

truncate { this.isNegative ? ceil : floor } // lower integer, towards zero

round { // nearer integer
if (isInteger) return this
var div = _n / _d
if (_d == 2) {
div = isNegative ? div.dec : div.inc // round 1/2 away from zero
return BigRat.new(div, BigInt.one)
}
return (this + BigRat.half).floor
}

fraction { this - truncate } // fractional part (same sign as this.num)

// Reciprocal
inverse { BigRat.new(_d, _n) }

// Integer division.
idiv(o) { (this/o).truncate }

// Negation.
-{ BigRat.new(-_n, _d) }

// Arithmetic operators (work with numbers and numeric strings as well as other rationals).
+(o) { (o = BigRat.check_(o)) && BigRat.new(_n * o.den + _d * o.num, _d * o.den) }
-(o) { (o = BigRat.check_(o)) && (this + (-o)) }
*(o) { (o = BigRat.check_(o)) && BigRat.new(_n * o.num, _d * o.den) }
/(o) { (o = BigRat.check_(o)) && BigRat.new(_n * o.den, _d * o.num) }
%(o) { (o = BigRat.check_(o)) && (this - idiv(o) * o) }

// Computes integral powers.
pow(i) {
if (!((i is Num) && i.isInteger)) Fiber.abort("Argument must be an integer.")
if (i == 0) return this
var np = _n.pow(i)
var dp = _d.pow(i)
return (i > 0) ? BigRat.new(np, dp) : BigRat.new(dp, np)
}

// Returns the square of the current instance.
square { BigRat.new(_n * _n , _d *_d) }

// Other methods.
inc { this + BigRat.one } // increment
dec { this - BigRat.one } // decrement
abs { (_n >= BigInt.zero) ? this : -this } // absolute value
sign { _n.sign } // sign

// The inherited 'clone' method just returns 'this' as BigRat objects are immutable.
// If you need an actual copy use this method instead.
copy() { BigRat.new(_n, _d) }
// Compares this BigRat with another one to enable comparison operators via Comparable trait.
compare(other) {
if ((other is Num) && other.isInfinity) return -other.sign
other = BigRat.check_(other)
if (_d == other.den) return _n.compare(other.num)
return (_n * other.den).compare(other.num * _d)
}

// As above but compares the absolute values of the BigRats.
compareAbs(other) { this.abs.compare(other.abs) }

// Returns this BigRat expressed as a BigInt with any fractional part truncated.
toBigInt { _n/_d }

// Converts the current instance to a Num where possible.
// Will probably lose accuracy if the numerator and/or denominator are not 'small'.
toFloat { Num.fromString(this.toDecimal(14)) }

// Converts the current instance to an integer where possible with any fractional part truncated.
// Will probably lose accuracy if the numerator and/or denominator are not 'small'.
toInt { this.toFloat.truncate }

// Returns the decimal representation of this BigRat object to 'digits' decimal places accuracy.
// Halves are rounded away from zero.
toDecimal(digits) {
if (!(digits is Num && digits.isInteger && digits >= 0)) {
Fiber.abort("Argument must be a non-negative integer")
}
var qr = _n.divMod(_d)
var intPart = qr[0].toString
if (this.isNegative && qr[0] == 0) intPart = "-" + intPart
var rem = BigRat.new(qr[1].abs, _d)
// need to allow an extra digit for subsequent rounding
var shiftedRem = rem * BigRat.new("1e" + (digits+1).toString, BigInt.one)
var decPart = (shiftedRem.num / shiftedRem.den).toString
var finalByte = decPart[-1].bytes[0]
if (finalByte >= 53) { // last character >= 5
decPart = (BigInt.new(decPart) + 5).toString
}
decPart = decPart[0...-1] // remove last digit
if (decPart.count < digits) {
decPart = ("0" * (digits - decPart.count + 1)) + decPart
}
if ((shiftedRem.num % shiftedRem.den) == BigInt.zero) {
decPart = decPart.trimEnd("0")
}
if (digits < 1) decPart = ""
if (decPart == "") return intPart
return intPart + "." + decPart
}

// Convenience version of the above which uses a default value of 14 decimal places accuracy.
toDecimal { toDecimal(14) }

// Returns a string represenation of this instance in the form "i_n/d" where 'i' is an integer.
toMixedString {
var q = _n / _d
var r = _n % _d
if (r.isNegative) r = -r
return q.toString + "_" + r.toString + "/" + _d.toString
}

// Returns the string representation of this BigRat object depending on 'showAsInt'.
toString { (BigRat.showAsInt && _d == BigRat.one) ? "%(_n)" : "%(_n)/%(_d)" }
}

/* BigRats contains various routines applicable to lists of big rational numbers */
class BigRats {
static sum(a) { a.reduce(BigRat.zero) { |acc, x| acc + x } }
static mean(a) { sum(a)/a.count }
static prod(a) { a.reduce(BigRat.one) { |acc, x| acc * x } }
static max(a) { a.reduce { |acc, x| (x > acc) ? x : acc } }
static max(a) { a.reduce { |acc, x| (x > acc) ? x : acc } }
static min(a) { a.reduce { |acc, x| (x < acc) ? x : acc } }
static min(a) { a.reduce { |acc, x| (x < acc) ? x : acc } }
Line 1,284: Line 1,564:


// Type aliases for classes in case of any name clashes with other modules.
// Type aliases for classes in case of any name clashes with other modules.
var Big_BigInt = BigInt
var Big_BigInt = BigInt
var Big_BigInts = BigInts
var Big_BigInts = BigInts
var Big_BigRat = BigRat
var Big_BigRats = BigRats
var Big_Comparable = Comparable // in case imported indirectly
var Big_Comparable = Comparable // in case imported indirectly