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 |
||