Mercurial > projects > decimal
changeset 0:42cf4db6be32
Creation
author | Paul (paul.d.anderson@comcast.net) |
---|---|
date | Sat, 13 Mar 2010 13:22:25 -0800 |
parents | |
children | a984d3056cc4 |
files | src/decimal/context.d src/decimal/decimal.d src/decimal/math.d |
diffstat | 3 files changed, 3949 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/decimal/context.d Sat Mar 13 13:22:25 2010 -0800 @@ -0,0 +1,182 @@ +module decimal.context; + +import decimal.decimal; +import std.string; + +//-------------------------- +// +// DecimalContext struct +// +//-------------------------- + +/** + * Enumeration of available rounding modes. + */ +public enum Rounding { + HALF_EVEN, + HALF_DOWN, + HALF_UP, + DOWN, + UP, + FLOOR, + CEILING, +} + + +// context flags +public enum : ubyte { + CLAMPED = 0x01, + DIVISION_BY_ZERO = 0x02, + INEXACT = 0x04, + INVALID_OPERATION = 0x08, + OVERFLOW = 0x10, + ROUNDED = 0x20, + SUBNORMAL = 0x40, + UNDERFLOW = 0x80 +} + +/+public: + static immutable ubyte CLAMPED = 0x01; + static immutable ubyte DIVISION_BY_ZERO = 0x02; + static immutable ubyte INEXACT = 0x04; + static immutable ubyte INVALID_OPERATION = 0x08; + static immutable ubyte OVERFLOW = 0x10; + static immutable ubyte ROUNDED = 0x20; + static immutable ubyte SUBNORMAL = 0x40; + static immutable ubyte UNDERFLOW = 0x80;+/ + +/** + * Context for Decimal mathematic operations + */ +public struct DecimalContext { + public: + Rounding mode = Rounding.HALF_EVEN; + uint precision = 9; + ubyte traps; + ubyte flags; + int eMin = -98; + int eMax = 99; + + const int eTiny() { + return eMin - (precision - 1); + } + + /// Returns the number of decimal digits in this context. + const uint dig() { + return precision; + } + + /// returns the smallest available increment to one in this context + const Decimal epsilon() { + return Decimal(1,-precision); + } + + /// Returns the number of binary digits in this context. + const int mant_dig() { + return cast(int)(precision/LOG2); + } + + const int min_exp() { + return cast(int)(eMin/LOG2); + } + + const int max_exp() { + return cast(int)(eMax/LOG2); + } + + const int min_10_exp() { + return eMin; + } + + const int max_10_exp() { + return eMax; + } + + /// Returns the maximum representable normal value in the current context. + // TODO: Replace this formula with a single execution when precision or eMax is changed. + const Decimal max() { + string cstr = "9." ~ repeat("9", precision-1) ~ "E" ~ format("%d", eMax); + return Decimal(cstr); + } + + // Returns the minimum representable normal value in the current context. + const Decimal min_normal() { + return Decimal(1, eMin); + } + + // Returns the minimum representable subnormal value in the current context. + const Decimal min() { + return Decimal(1, eTiny); + } + +/+ public const int getPrecision() { + return precision; + } + + public void setPrecision(int precision) { + this.precision = precision; + }+/ + + const void setFlag(const ubyte flag, const bool value = true) { + if (value) { + context.flags |= flag; + } + else { + context.flags &= !flag; + } + } + + public bool getFlag(const ubyte flag) { + return (context.flags & flag) == flag; + } + + public void clearFlags() { + context.flags = 0; + } + + +}; // end struct DecimalContext + +public: + // default context + immutable DecimalContext DEFAULT_CONTEXT = DecimalContext(); + +private: + // context stack +struct ContextStack { + private: + DecimalContext[] stack = [ DEFAULT_CONTEXT ]; + uint capacity = 1; + uint count = 1; + + public void push() { + if (count >= capacity) { + capacity *= 2; + stack.length = capacity; + } + count++; + stack[count-1] = context; + } + + public void pop() { + if (count == 0) { + push(); + } + if (count == 1) { + context = stack[0]; + } + else { + count--; + context = stack[count-1]; + } + } + +} // end struct ContextStack + +//-------------------------- +// +// Emd of DecimalContext struct +// +//-------------------------- + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/decimal/decimal.d Sat Mar 13 13:22:25 2010 -0800 @@ -0,0 +1,3241 @@ +// TODO: ensure context flags are being set and cleared properly. + +// TODO: add exp() add sqrt() add power(): + +// TODO: add a set/getPayload to Decimal + +// TODO: unittest opPostDec && opPostInc. + +// TODO: this(str): add tests for just over/under int.max, int.min + +// TODO: organize by structs, lib functions modules functions, etc. + +// TODO: opEquals unit test should include numerically equal testing. + +// TODO: write some test cases for flag setting. test the add/sub/mul/div functions + +// TODO: to/from real or double (float) values needs definition and implementation. + +// TODO: need to determine the property name for dig and/or digits + +module decimal.decimal; + +import decimal.context; +import std.bigint; +import std.conv; +import std.ctype: isdigit; +import std.math: PI, LOG2; +import std.stdio: write, writeln; +import std.string; + +// special values +private enum SpVal {CLEAR, ZERO, INF, QNAN, SNAN}; + +// common decimal "numbers" +public: + static immutable Decimal ONE = {spval:SpVal.CLEAR, ceff:{[1]}}; + static immutable Decimal TEN = {spval:SpVal.CLEAR, ceff:{[10]}}; + static immutable Decimal NaN = {spval:SpVal.QNAN}; + static immutable Decimal POS_INF = {spval:SpVal.INF}; + static immutable Decimal NEG_INF = {sign:true, SpVal.INF}; + static immutable Decimal ZERO = {spval:SpVal.ZERO}; + static immutable Decimal NEG_ZERO = {sign:true, SpVal.ZERO}; + +// active context +public DecimalContext context = DEFAULT_CONTEXT; + +// singleton ContextStack +private ContextStack contextStack; + +/// saves the current context +public void pushContext() { + contextStack.push(); +} + +/// restores the previous context +public void popContext() { + contextStack.pop(); +} + +/** + * A struct representing an arbitrary-precision floating-point number. + * + * The implementation follows the General Decimal Arithmetic + * Specification, Version 1.70 (25 Mar 2009), + * http://www.speleotrove.com/decimal. This specification conforms with + * IEEE standard 754-2008. + */ +struct Decimal { + +private: + SpVal spval = SpVal.QNAN; // special values: default value is quiet NaN + bool sign = false; // true if the value is negative, false otherwise. + int expo = 0; // the exponent of the Decimal value + BigInt ceff; // the coefficient of the Decimal value + // NOTE: not a uint -- causes math problems down the line. + int digits; // the number of decimal digits in this number. + // (unless the number is a special value) + +//-------------------------------- +// construction +//-------------------------------- + +public: + /** + * Constructs a Decimal number from a sign, a BigInt coefficient and + * an integer exponent. + * The precision of the number is deduced from the number of decimal + * digits in the coefficient. + */ + this(const bool sign, const BigInt coefficient, const int exponent) { + this.clear(); + if (coefficient < BIG_ZERO) { + this.sign = !sign; + this.ceff = -coefficient; + } + else { + this.sign = sign; + this.ceff = coefficient; + if (coefficient == BIG_ZERO) { + this.spval = SpVal.ZERO; + } + } + this.expo = exponent; + this.digits = numDigits(this.ceff, 1); + } + + /** + * Constructs a Decimal number from a sign, an integer coefficient and + * an integer exponent. + */ + this(const bool sign, const int coefficient, const int exponent) { + this(sign, BigInt(coefficient), exponent); + } + + /** + * Constructs a Decimal number from a sign, a special value string and + * an optional payload. + */ + this(const bool sign, string str, const uint payload = 0) { + this.clear();; + this.sign = sign; + if (icmp(str, "inf") == 0 || icmp(str, "infinity") == 0) { + spval = SpVal.INF; + return; + } + if (icmp(str, "snan") == 0) { + spval = SpVal.SNAN; + } + else { + spval = SpVal.QNAN; + } + ceff = payload; + } + + /** + * Constructs a Decimal from a BigInt coefficient and an int exponent. + * The sign of the number is the sign of the coefficient. + */ + this(const BigInt coefficient, const int exponent) { + this(false, coefficient, exponent); + }; + + /** + * Constructs a Decimal from a BigInt. + */ + this(const BigInt coefficient) { + this(coefficient, 0); + }; + + /** + * Constructs a Decimal from an integer coefficient and an integer exponent. + */ + this(const long coefficient, const int exponent) { + this(BigInt(coefficient), exponent); + } + + /** + * Constructs a Decimal from an integer value. + */ + this(const long coefficient) { + this(BigInt(coefficient), 0); + } + + /** + * Constructs a Decimal from an integer coefficient, exponent and precision. + */ + // TODO: should this set flags? probably not. + this(const long coefficient, const int exponent, const int precision) { + this(coefficient, exponent); + pushContext(); + context.precision = precision; + setDigits(this); + popContext(); + } + + /** + * Constructs a Decimal from a real value. + */ + this(const real r) { + string str = format("%.*G", cast(int)context.precision, r); + this = str; + } + + /** + * Constructs a Decimal from a double value. + * Set to the specified precision + */ + this(const real r, int precision) { + string str = format("%.*E", precision, r); + this = str; + } + + // copy constructor + this(const Decimal that) { + this = that; + }; + + // construct from string representation + this(const string str) { + this = str; + }; + +unittest { + write("construction."); + Decimal f = Decimal(BigInt(1234), 567); + assert(f.toString() == "1.234E+570"); + f = Decimal(BigInt(1234)); + assert(f.toString() == "1234"); + f = Decimal(BigInt(123400)); + assert(f.toString() == "123400"); + f = Decimal(1234, 567); + assert(f.toString() == "1.234E+570"); + f = Decimal(BigInt(1234)); + assert(f.toString() == "1234"); + f = Decimal(1234, 0, 9); + assert(f.toString() == "1234.00000"); + Decimal dec = Decimal(1234, 1, 9); + assert(dec.toString() == "12340.0000"); + dec = Decimal(12, 1, 9); + assert(dec.toString() == "120.000000"); + dec = Decimal(int.max, -4, 9); + assert(dec.toString() == "214748.365"); + dec = Decimal(int.max, -4); + assert(dec.toString() == "214748.3647"); + dec = Decimal(1234567, -2, 5); + assert(dec.toString() == "12346"); + writeln("passed"); +} + +unittest { + write("this(str)...."); + Decimal f; + string str = "0"; + f = str; + assert(f.toString() == str); + assert(f.toAbstract() == "[0,0,0]"); + str = "0.00"; + f = str; + assert(f.toString() == str); + assert(f.toAbstract() == "[0,0,-2]"); + str = "0.0"; + f = str; + assert(f.toString() == str); + assert(f.toAbstract() == "[0,0,-1]"); + f = "0."; + assert(f.toString() == "0"); + assert(f.toAbstract() == "[0,0,0]"); + f = ".0"; + assert(f.toString() == "0.0"); + assert(f.toAbstract() == "[0,0,-1]"); + str = "1.0"; + f = str; + assert(f.toString() == str); + assert(f.toAbstract() == "[0,10,-1]"); + str = "1."; + f = str; + assert(f.toString() == "1"); + assert(f.toAbstract() == "[0,1,0]"); + str = ".1"; + f = str; + assert(f.toString() == "0.1"); + assert(f.toAbstract() == "[0,1,-1]"); + f = Decimal("123"); + assert(f.toString() == "123"); + f = Decimal("-123"); + assert(f.toString() == "-123"); + f = Decimal("1.23E3"); + assert(f.toString() == "1.23E+3"); + f = Decimal("1.23E"); + assert(f.toString() == "NaN"); + f = Decimal("1.23E-"); + assert(f.toString() == "NaN"); + f = Decimal("1.23E+"); + assert(f.toString() == "NaN"); + f = Decimal("1.23E+3"); + assert(f.toString() == "1.23E+3"); + f = Decimal("1.23E3B"); + assert(f.toString() == "NaN"); + f = Decimal("12.3E+007"); + assert(f.toString() == "1.23E+8"); + f = Decimal("12.3E+70000000000"); + assert(f.toString() == "NaN"); + f = Decimal("12.3E+7000000000"); + assert(f.toString() == "NaN"); + f = Decimal("12.3E+700000000"); +// writeln(f.toString()); + assert(f.toString() == "1.23E+700000001"); + f = Decimal("12.3E-700000000"); +// writeln(f.toString()); + assert(f.toString() == "1.23E-699999999"); + // NOTE: since there will still be adjustments -- maybe limit to 99999999? + f = Decimal("12.0"); + assert(f.toString() == "12.0"); + f = Decimal("12.3"); + assert(f.toString() == "12.3"); + f = Decimal("1.23E-3"); + assert(f.toString() == "0.00123"); + f = Decimal("0.00123"); + assert(f.toString() == "0.00123"); + f = Decimal("-1.23E-12"); + assert(f.toString() == "-1.23E-12"); + f = Decimal("-0"); + assert(f.toString() == "-0"); + f = Decimal("inf"); + assert(f.toString() == "Infinity"); + f = Decimal("NaN"); + assert(f.toString() == "NaN"); + f = Decimal("-NaN"); + assert(f.toString() == "-NaN"); + f = Decimal("sNaN"); + assert(f.toString() == "sNaN"); + f = Decimal("Fred"); + assert(f.toString() == "NaN"); + writeln("passed"); +} + + /** + * dup property + */ + const Decimal dup() { + Decimal cp; + cp.sign = sign; + cp.spval = spval; + cp.ceff = ceff; + cp.expo = expo; + return cp; + } + +//-------------------------------- +// assignment +//-------------------------------- + + /// Assigns a Decimal (makes a copy) + void opAssign(const Decimal that) { + this.sign = that.sign; + this.spval = that.spval; + this.digits = that.digits; + this.expo = that.expo; + this.ceff = that.ceff; + } + + /// Assigns a floating point value. + void opAssign(const real r) { + this = Decimal(r); + } + + /// Assign an integer value + void opAssign(const long n) { + spval = (n == 0) ? SpVal.ZERO : SpVal.CLEAR; + sign = n < 0; + ceff = sign ? -n : n; + expo = 0; + digits = numDigits(ceff, 1); + } + + /// Assign a BigInt + void opAssign(const BigInt big) { + spval = (big == BIG_ZERO) ? SpVal.ZERO : SpVal.CLEAR; + sign = big < 0; + ceff = sign ? -big : big; + expo = 0; + digits = numDigits(ceff, 1); + } + + /// Assigns a string + void opAssign(const string numeric_string) { + clear(); + sign = false; + + // strip, copy, tolower + char[] str = strip(numeric_string).dup; + tolowerInPlace(str); + + // get sign, if any + if (startsWith(str,"-")) { + sign = true; + str = str[1..$]; + } + else if (startsWith(str,"+")) { + str = str[1..$]; + } + + // check for NaN + if (startsWith(str,"nan")) { + spval = SpVal.QNAN; + if (str == "nan") { + ceff = BIG_ZERO; + return; + } + // set payload + str = str[3..$]; + // ensure string is all digits + foreach(char c; str) { + if (!isdigit(c)) { + return; + } + } + // convert string to payload + ceff = BigInt(str.idup); + return; + }; + + // check for sNaN + if (startsWith(str,"snan")) { + spval = SpVal.SNAN; + if (str == "snan") { + ceff = BIG_ZERO; + return; + } + // set payload + str = str[4..$]; + // ensure string is all digits + foreach(char c; str) { + if (!isdigit(c)) { + return; + } + } + // convert string to payload + ceff = BigInt(str.idup); + return; + }; + + // check for infinity + if (str == "inf" || str == "infinity") { + spval = SpVal.INF; + return; + }; + + clear(); + // check for exponent + int pos = find(str, 'e'); + if (pos > 0) { + // if it's just a trailing 'e', return NaN + if (pos == str.length - 1) { + spval = SpVal.QNAN; + return; + } + // split the string into coefficient and exponent + char[] xstr = str[pos+1..$]; + str = str[0..pos]; + // assume exponent is positive + bool xneg = false; + // check for minus sign + if (startsWith(xstr, "-")) { + xneg = true; + xstr = xstr[1..$]; + } + // check for plus sign + else if (startsWith(xstr, "+")) { + xstr = xstr[1..$]; + } + + // ensure it's not now empty + if (xstr.length < 1) { + spval = SpVal.QNAN; + return; + } + + // ensure exponent is all digits + foreach(char c; xstr) { + if (!isdigit(c)) { + spval = SpVal.QNAN; + return; + } + } + + // trim leading zeros + while (xstr[0] == '0' && xstr.length > 1) { + xstr = xstr[1..$]; + } + + // make sure it will fit into an int + if (xstr.length > 10) { + spval = SpVal.QNAN; + return; + } + if (xstr.length == 10) { + // try to convert it to a long (should work) and + // then see if the long value is too big (or small) + long lex = to!long(xstr); + if ((xneg && (-lex < int.min)) || lex > int.max) { + spval = SpVal.QNAN; + return; + } + expo = cast(int) lex; + } + else { + // everything should be copacetic at this point + expo = to!int(xstr); + } + if (xneg) { + expo = -expo; + } + } + else { + expo = 0; + } + + // remove trailing decimal point + if (endsWith(str, ".")) { + str = str[0..$-1]; + } + // strip leading zeros + while (str[0] == '0' && str.length > 1) { + str = str[1..$]; + } + + // remove internal decimal point + int point = find(str, '.'); + if (point >= 0) { + // excise the point and adjust exponent + str = str[0..point] ~ str[point+1..$]; + int diff = str.length - point; + expo -= diff; + } + + // ensure string is not empty + if (str.length < 1) { + spval = SpVal.QNAN; + return; + } + + // ensure string is all digits + foreach(char c; str) { + if (!isdigit(c)) { + spval = SpVal.QNAN; + return; + } + } + // convert string to BigInt + ceff = BigInt(str.idup); + digits = numDigits(ceff, str.length); + if (ceff == BIG_ZERO) spval = SpVal.ZERO; + + }; // end opAssign(string) + +//-------------------------------- +// string representations +//-------------------------------- + +/** + * Converts a Decimal to an abstract string representation. + */ +private const string toAbstract() { + switch (spval) { + case SpVal.SNAN: + string payload = ceff == BIG_ZERO ? "" : "," ~ ceff.toString(); + return format("[%d,%s%s]", sign ? 1 : 0, "sNaN", payload); + case SpVal.QNAN: + string payload = ceff == BIG_ZERO ? "" : "," ~ ceff.toString(); + return format("[%d,%s%s]", sign ? 1 : 0, "qNaN", payload); + case SpVal.INF: + return format("[%d,%s]", sign ? 1 : 0, "inf"); + default: + return format("[%d,%s,%d]", sign ? 1 : 0, ceff.toString(), expo); + } +} + +/** + * Converts a Decimal to a string representation. + */ +const string toString() { + + // string representation of special values + if (spval > SpVal.ZERO) { + string str; + switch(spval) { + case SpVal.ZERO: + str = "0"; + break; + case SpVal.INF: + str = "Infinity"; + break; + case SpVal.SNAN: + str = "sNaN"; + break; + default: + str = "NaN"; + } + if (spval >= SpVal.QNAN && ceff != BIG_ZERO) { + str ~= ceff.toString(); + } + return sign ? "-" ~ str : str; + } + + // string representation of finite numbers + string cstr = ceff.toString(); + int clen = cstr.length; + int adjx = expo + clen - 1; + + // if exponent is small, don't use exponential notation + if (expo <= 0 && adjx >= -6) { + // if exponent is not zero, insert a decimal point + if (expo != 0) { + int point = std.math.abs(expo); + // if coefficient is too small, pad with zeroes + if (point > clen) { + cstr = zfill(cstr, point); + clen = cstr.length; + } + // if no chars precede the decimal point, prefix a zero + if (point == clen) { + cstr = "0." ~ cstr; + } + // otherwise insert a decimal point + else { + cstr = insert(cstr, cstr.length - point, "."); + } + } + return sign ? "-" ~ cstr : cstr; + } + // use exponential notation +// else { + if (clen > 1) { + cstr = insert(cstr, 1, "."); + } + string xstr = to!string(adjx); + if (adjx >= 0) { + xstr = "+" ~ xstr; + } + string str = cstr ~ "E" ~ xstr; + if (sign) { + return "-" ~ str; + } + else { + return str; + } +// } // end else + +}; // end toString() + + /** + * Converts a Decimal to an engineering string representation. + */ + const string toEngString() { + return toString(); + }; + + /** + * Converts a Decimal to a scientific string representation. + */ + const string toSciString() { + return toString(); + }; + +unittest { + write("to-sci-str..."); + Decimal dec = Decimal(false, 123, 0); + assert(dec.toString() == "123"); + assert(dec.toAbstract() == "[0,123,0]"); + dec = Decimal(true, 123, 0); + assert(dec.toString() == "-123"); + assert(dec.toAbstract() == "[1,123,0]"); + dec = Decimal(false, 123, 1); + assert(dec.toString() == "1.23E+3"); + assert(dec.toAbstract() == "[0,123,1]"); + dec = Decimal(false, 123, 3); + assert(dec.toString() == "1.23E+5"); + assert(dec.toAbstract() == "[0,123,3]"); + dec = Decimal(false, 123, -1); + assert(dec.toString() == "12.3"); + assert(dec.toAbstract() == "[0,123,-1]"); + dec = Decimal(false, 123, -5); + assert(dec.toString() == "0.00123"); + assert(dec.toAbstract() == "[0,123,-5]"); + dec = Decimal(false, 123, -10); + assert(dec.toString() == "1.23E-8"); + assert(dec.toAbstract() == "[0,123,-10]"); + dec = Decimal(true, 123, -12); + assert(dec.toString() == "-1.23E-10"); + assert(dec.toAbstract() == "[1,123,-12]"); + dec = Decimal(false, 0, 0); + assert(dec.toString() == "0"); + assert(dec.toAbstract() == "[0,0,0]"); + dec = Decimal(false, 0, -2); + assert(dec.toString() == "0.00"); + assert(dec.toAbstract() == "[0,0,-2]"); + dec = Decimal(false, 0, 2); + assert(dec.toString() == "0E+2"); + assert(dec.toAbstract() == "[0,0,2]"); + dec = Decimal(true, 0, 0); + assert(dec.toString() == "-0"); + assert(dec.toAbstract() == "[1,0,0]"); + dec = Decimal(false, 5, -6); + assert(dec.toString() == "0.000005"); + assert(dec.toAbstract() == "[0,5,-6]"); + dec = Decimal(false, 50,-7); + assert(dec.toString() == "0.0000050"); + assert(dec.toAbstract() == "[0,50,-7]"); + dec = Decimal(false, 5, -7); + assert(dec.toString() == "5E-7"); + assert(dec.toAbstract() == "[0,5,-7]"); + dec = Decimal(false, "inf"); + assert(dec.toString() == "Infinity"); + assert(dec.toAbstract() == "[0,inf]"); + dec = Decimal(true, "inf"); + assert(dec.toString() == "-Infinity"); + assert(dec.toAbstract() == "[1,inf]"); + dec = Decimal(false, "NaN"); + assert(dec.toString() == "NaN"); + assert(dec.toAbstract() == "[0,qNaN]"); + dec = Decimal(false, "NaN", 123); + assert(dec.toString() == "NaN123"); + assert(dec.toAbstract() == "[0,qNaN,123]"); + dec = Decimal(true, "sNaN"); + assert(dec.toString() == "-sNaN"); + assert(dec.toAbstract() == "[1,sNaN]"); + writeln("passed"); +} + + +//-------------------------------- +// member properties +//-------------------------------- + + /// returns the exponent of this Decimal + const int exponent() { + return this.expo; + } + /// returns the coefficient of this Decimal + const BigInt coefficient() { + return this.ceff; + } + + /// returns the sign of this Decimal + const int sgn() { + if (isZero) return 0; + return sign ? -1 : 1; + } + + /// returns a Decimal with the same exponent as this Decimal + /// and a coefficient of 1. + const Decimal quantum() { + return Decimal(1, this.expo); + } + +//-------------------------------- +// floating point properties +//-------------------------------- + + static int precision() { + return context.precision; + } + + /// returns the default value for this type (NaN) + static Decimal init() { + return NaN; + } + + /// Returns NaN + static Decimal nan() { + return NaN; + } + + /// Returns positive infinity. + static Decimal infinity() { + return POS_INF; + } + +//-------------------------------- +// classification properties +//-------------------------------- + + /** + * Returns true if this number is canonical representation (always true). + */ + const bool isCanonical() { + return true; + } + + /** + * Returns true if this Decimal is +/- zero. + */ + const bool isZero() { + return spval == SpVal.ZERO; + } + + /** + * Returns true if this Decimal is a quiet or signaling NaN. + */ + const bool isNaN() { + return this.spval == SpVal.QNAN || this.spval == SpVal.SNAN; + } + + /** + * Returns true if this Decimal is a signaling NaN. + */ + const bool isSignaling() { + return this.spval == SpVal.SNAN; + } + + /** + * Returns true if this Decimal is a quiet NaN. + */ + const bool isQuiet() { + return this.spval == SpVal.QNAN; + } + + /** + * Returns true if this Decimal is +/- infinity. + */ + const bool isInfinite() { + return this.spval == SpVal.INF; + } + + /** + * Returns true if this Decimal is not +/- infinity and not a NaN. + */ + const bool isFinite() { + return spval != SpVal.INF + && spval != SpVal.QNAN + && spval != SpVal.SNAN; + } + + /** + * Returns true if this Decimal is a NaN or infinity. + */ + const bool isSpecial() { + return spval == SpVal.INF + || spval == SpVal.QNAN + || spval == SpVal.SNAN; + } + + /** + * Returns true if this Decimal is negative. (Includes -0) + */ + const bool isSigned() { + return this.sign; + } + + /** + * Returns true if this Decimal is subnormal. + */ + const bool isSubnormal() { + if (spval != SpVal.CLEAR) return false; + return adjustedExponent < context.eMin; + } + + /** + * Returns true if this Decimal is subnormal. + */ + const bool isNormal() { + if (spval != SpVal.CLEAR) return false; + return adjustedExponent >= context.eMin; + } + +//-------------------------------- +// comparison +//-------------------------------- + + /** + * Returns -1, 0 or 1, if this number is less than, equal to or + * greater than the argument, respectively. + */ + int opCmp(const Decimal that) { + return icompare(this, that); + } + + /** + * Returns true if this Decimal is equal to the specified Decimal. + * A NaN is not equal to any number, not even to another NaN. + * Infinities are equal if they have the same sign. + * Zeros are equal regardless of sign. + * Finite numbers are equal if they are numerically equal to the current precision. + * A Decimal may not be equal to itself (this != this) if it is a NaN. + */ + const bool opEquals(ref const Decimal that) { + // if either is NaN... + if (this.isNaN || that.isNaN) return false; + + // if either is infinite... + if (this.isInfinite || that.isInfinite) { + return (this.spval == that.spval && this.sign == that.sign); + } + + // if either is zero... + if (this.isZero || that.isZero) { + return (this.isZero && that.isZero); + } + // if their signs differ + if (this.sign != that.sign) { + return false; + } + + // if they have the same representation, they are equal + if (this.expo == that.expo && this.ceff == that.ceff) { + return true; + } + + // otherwise they are equal if they represent the same value + // NOTE: this is only a check to current precision. + Decimal result = this - that; + return result.isZero; + } + +unittest { + write("equals......."); + Decimal op1; + Decimal op2; + op1 = "NaN"; + op2 = "NaN"; + assert(op1 != op2); + op1 = "inf"; + op2 = "inf"; + assert(op1 == op2); + op2 = "-inf"; + assert(op1 != op2); + op1 = "-inf"; + assert(op1 == op2); + op2 = "NaN"; + assert(op1 != op2); + op1 = 0; + assert(op1 != op2); + op2 = 0; + assert(op1 == op2); + writeln("passed"); +} + +//-------------------------------- +// unary arithmetic operators +//-------------------------------- + + /** + * unary minus -- returns a copy with the opposite sign. + * This operation may set flags -- equivalent to + * subtract('0', b); + */ + const Decimal opNeg() { + return minus(this); + } + + /** + * unary plus -- returns a copy. + * This operation may set flags -- equivalent to + * add('0', a); + */ + const Decimal opPos() { + return plus(this); + } + + /** + * Returns this + 1. + */ + Decimal opPostInc() { + this += ONE; + return this; + } + + /** + * Returns this - 1. + */ + Decimal opPostDec() { + this -= ONE; + return this; + } + +//-------------------------------- +// binary arithmetic operators +//-------------------------------- + + /// Adds a Decimal to this and returns the Decimal result + const Decimal opAdd(T:Decimal)(const T addend) { + return add(this, addend); + } + + // Adds a number to this and returns the result. + const Decimal opAdd(T)(const T addend) { + return add(this, Decimal(addend), context); + } + + const Decimal opSub(T:Decimal)(const T subtrahend) { + return subtract(this, subtrahend); + } + + const Decimal opSub(T)(const T subtrahend) { + return subtract(this, Decimal(subtrahend), context); + } + + const Decimal opMul(T:Decimal)(const T factor) { + return multiply(this, factor); + } + + const Decimal opMul(T)(const T factor) { + return multiply(this, Decimal(factor)); + } + + const Decimal opDiv(T:Decimal)(const T divisor) { + return divide(this, divisor); + } + + const Decimal opDiv(T)(const T divisor) { + return divide(this, Decimal(divisor)); + } + + const Decimal opMod(T:Decimal)(const T divisor) { + return remainder(this, divisor); + } + + const Decimal opMod(T)(const T divisor) { + return remainder(this, Decimal(divisor)); + } + +//-------------------------------- +// arithmetic assignment operators +//-------------------------------- + + Decimal opAddAssign(T)(const T addend) { + this = this + addend; + return this; + } + + Decimal opSubAssign(T)(const T subtrahend) { + this = this - subtrahend; + return this; + } + + Decimal opMulAssign(T)(const T factor) { + this = this * factor; + return this; + } + + Decimal opDivAssign(T)(const T divisor) { + this = this / divisor; + return this; + } + + Decimal opModAssign(T)(const T divisor) { + this = this % divisor; + return this; + } + +//----------------------------- +// nextUp, nextDown, nextAfter +//----------------------------- + + const Decimal nextUp() { + return nextPlus(this); + } + + const Decimal nextDown() { + return nextMinus(this); + } + + const Decimal nextAfter(const Decimal dcm) { + return nextToward(this, dcm); + } + +private: + /** + * clears the special value flags + */ + void clear() { + spval = SpVal.CLEAR; + } + + const int adjustedExponent() { + return expo + digits - 1; + } + + const bool overflow() { + return adjustedExponent > context.eMax; + } + + unittest{ + write("overflow....."); + Decimal dec = Decimal(123, 99); + assert(dec.overflow); + dec = Decimal(12, 99); + assert(dec.overflow); + dec = Decimal(1, 99); + assert(!dec.overflow); + dec = Decimal(9, 99); + assert(!dec.overflow); + writeln("passed"); + } + +} // end struct Decimal + + +//-------------------------------- +// context functions +//-------------------------------- + +// TODO: this is actually a property of the context. + +/** + * Returns radix of this representation (10). + */ +public int radix() { + return 10; +} + +unittest { + write("radix........"); + assert(radix() == 10); + writeln("passed"); +} + +//-------------------------------- +// classification functions +//-------------------------------- + +public string classify(const Decimal dcm) { + if (dcm.isSignaling()) { + return "sNaN"; + } + if (dcm.isQuiet) { + return "NaN"; + } + if (dcm.isInfinite) { + return dcm.sign ? "-Infinity" : "+Infinity"; + } + if (dcm.isSubnormal) { + return dcm.sign ? "-Subnormal" : "+Subnormal"; + } + if (dcm.isZero) { + return dcm.sign ? "-Zero" : "+Zero"; + } + return dcm.sign ? "-Normal" : "+Normal"; +} + +/** + * Returns true if this number is canonical representation (always true). + */ +public bool isCanonical(const Decimal dcm) { + return dcm.isCanonical; +} + +/** + * Returns true if this Decimal is a signaling NaN. + */ +public bool isSignaling(const Decimal dcm) { + return dcm.isSignaling; +} + +/** + * Returns true if the specified Decimal is a quiet NaN. + */ +public bool isQuiet(const Decimal dcm) { + return dcm.isQuiet; +} + +public bool isFinite(const Decimal dcm) { + return dcm.isFinite; +} + +public bool isInfinite(const Decimal dcm) { + return dcm.isInfinite; +} + +public bool isNaN(const Decimal dcm) { + return dcm.isNaN; +} + +public bool isNormal(const Decimal dcm) { + return dcm.isNormal; +} + +public bool isSubnormal(const Decimal dcm) { + return dcm.isSubnormal; +} + +public bool isZero(const Decimal dcm) { + return dcm.isZero; +} + +public bool isSigned(const Decimal dcm) { + return dcm.isSigned; +} + +unittest { + write("classify....."); + Decimal dcm; + dcm = "Infinity"; + assert(classify(dcm) == "+Infinity"); + dcm = "1E-10"; + assert(classify(dcm) == "+Normal"); + dcm = "2.50"; + assert(classify(dcm) == "+Normal"); + dcm = "0.1E-99"; + assert(classify(dcm) == "+Subnormal"); + dcm = "0"; + assert(classify(dcm) == "+Zero"); + dcm = "-0"; + assert(classify(dcm) == "-Zero"); + dcm = "-0.1E-99"; + assert(classify(dcm) == "-Subnormal"); + dcm = "-1E-10"; + assert(classify(dcm) == "-Normal"); + dcm = "-2.50"; + assert(classify(dcm) == "-Normal"); + dcm = "-Infinity"; + assert(classify(dcm) == "-Infinity"); + dcm = "NaN"; + assert(classify(dcm) == "NaN"); + dcm = "-NaN"; + assert(classify(dcm) == "NaN"); + dcm = "sNaN"; + assert(classify(dcm) == "sNaN"); + + dcm = Decimal("2.50"); + assert(isCanonical(dcm)); + + dcm = Decimal("2.50"); + assert(isFinite(dcm)); + dcm = Decimal("-0.3"); + assert(isFinite(dcm)); + dcm = 0; + assert(isFinite(dcm)); + dcm = Decimal("Inf"); + assert(!isFinite(dcm)); + dcm = Decimal("-Inf"); + assert(!isFinite(dcm)); + dcm = Decimal("NaN"); + assert(!isFinite(dcm)); + + dcm = Decimal("2.50"); + assert(!isInfinite(dcm)); + dcm = Decimal("-Inf"); + assert(isInfinite(dcm)); + dcm = Decimal("NaN"); + assert(!isInfinite(dcm)); + + dcm = Decimal("2.50"); + assert(!isNaN(dcm)); + dcm = Decimal("NaN"); + assert(isNaN(dcm)); + dcm = Decimal("-sNaN"); + assert(isNaN(dcm)); + + dcm = Decimal("2.50"); + assert(isNormal(dcm)); + dcm = Decimal("0.1E-99"); + assert(!isNormal(dcm)); + dcm = Decimal("0.00"); + assert(!isNormal(dcm)); + dcm = Decimal("-Inf"); + assert(!isNormal(dcm)); + dcm = Decimal("NaN"); + assert(!isNormal(dcm)); + writeln("passed"); + + dcm = Decimal("2.50"); + assert(!isQuiet(dcm)); + dcm = Decimal("NaN"); + assert(isQuiet(dcm)); + dcm = Decimal("sNaN"); + assert(!isQuiet(dcm)); + + dcm = Decimal("2.50"); + assert(!isSignaling(dcm)); + dcm = Decimal("NaN"); + assert(!isSignaling(dcm)); + dcm = Decimal("sNaN"); + assert(isSignaling(dcm)); + + dcm = Decimal("2.50"); + assert(!isSigned(dcm)); + dcm = Decimal("-12"); + assert(isSigned(dcm)); + dcm = Decimal("-0"); + assert(isSigned(dcm)); + + dcm = Decimal("2.50"); + assert(!isSubnormal(dcm)); + dcm = Decimal("0.1E-99"); + assert(isSubnormal(dcm)); + dcm = Decimal("0.00"); + assert(!isSubnormal(dcm)); + dcm = Decimal("-Inf"); + assert(!isSubnormal(dcm)); + dcm = Decimal("NaN"); + assert(!isSubnormal(dcm)); + + dcm = Decimal("0"); + assert(isZero(dcm)); + dcm = Decimal("2.50"); + assert(!isZero(dcm)); + dcm = Decimal("-0E+2"); + assert(isZero(dcm)); + +} + +//-------------------------------- +// copy functions +//-------------------------------- + +/** + * Returns a copy of the operand. + * The copy is unaffected by context; no flags are changed. + */ +Decimal copy(const Decimal dcm) { + Decimal cpy = dcm; + return cpy; +} + +/** + * Returns a copy of the operand with a positive sign. + * The copy is unaffected by context; no flags are changed. + */ +Decimal copyAbs(const Decimal dcm) { + Decimal cpy = dcm; + cpy.sign = false; + return cpy; +} + +/** + * Returns a copy of the operand with the sign inverted. + * The copy is unaffected by context; no flags are changed. + */ +Decimal copyNegate(const Decimal dcm) { + Decimal cpy = dcm; + cpy.sign = !dcm.sign; + return cpy; +} + +/** + * Returns a copy of the first operand with the sign of the second operand. + * The copy is unaffected by context; no flags are changed. + */ +Decimal copySign(const Decimal dcm1, const Decimal dcm2) { + Decimal cpy = dcm1; + cpy.sign = dcm2.sign; + return cpy; +} + +// TODO: these should actually be compare-total assertions +// This is probably true of other unit tests as well +unittest { + write("copy........."); + Decimal dcm; + Decimal expd; + dcm = "2.1"; + expd = "2.1"; + assert(copy(dcm) == expd); + dcm = "-1.00"; + expd = "-1.00"; + assert(copy(dcm) == expd); + dcm = "2.1"; + expd = "2.1"; + + assert(copyAbs(dcm) == expd); + dcm = "-1.00"; + expd = "1.00"; + assert(copyAbs(dcm) == expd); + dcm = "101.5"; + expd = "-101.5"; + + assert(copyNegate(dcm) == expd); + Decimal dcm1; + Decimal dcm2; + dcm1 = "1.50"; + dcm2 = "7.33"; + expd = "1.50"; + + assert(copySign(dcm1, dcm2) == expd); + dcm1 = "-1.50"; + dcm2 = "7.33"; + expd = "1.50"; + assert(copySign(dcm1, dcm2) == expd); + dcm1 = "1.50"; + dcm2 = "-7.33"; + expd = "-1.50"; + assert(copySign(dcm1, dcm2) == expd); + dcm1 = "-1.50"; + dcm2 = "-7.33"; + expd = "-1.50"; + assert(copySign(dcm1, dcm2) == expd); + writeln("passed"); +} + +//-------------------------------- +// plus, minus and abs functions +//-------------------------------- + +/** + * unary minus -- returns a copy with the opposite sign. + * This operation may set flags -- equivalent to + * subtract('0', b); + */ +Decimal minus(const Decimal dcm) { + Decimal result; + if(isInvalidOperation(dcm, result)) { + return result; + } + result = copyNegate(dcm); + round(result); + return result; +} + +/** + * unary plus -- returns a copy. + * This operation may set flags -- equivalent to + * add('0', a); + */ +Decimal plus(const Decimal dcm) { + Decimal result; + if(isInvalidOperation(dcm, result)) { + return result; + } + result = dcm; + round(result); + return result; +} + +unittest { + write("minus/plus..."); + // NOTE: result should equal 0+this or 0-this + Decimal zero = Decimal(0); + Decimal dcm; + Decimal expd; + dcm = "1.3"; + expd = zero + dcm; + assert(+dcm == expd); + dcm = "-1.3"; + expd = zero + dcm; + assert(+dcm == expd); + dcm = "1.3"; + expd = zero - dcm; + assert(-dcm == expd); + dcm = "-1.3"; + expd = zero - dcm; + assert(-dcm == expd); + // TODO: add tests that check flags. + writeln("passed"); +} + +/// Returns a new Decimal equal to the absolute value of this Decimal. +public Decimal abs(const Decimal dcm) { + Decimal result; + if(isInvalidOperation(dcm, result)) { + return result; + } + result = copyAbs(dcm); + round(result); + return result; +} + +unittest { + // TODO: add rounding tests + write("abs.........."); + Decimal dcm; + Decimal expd; + dcm = "sNaN"; + assert(abs(dcm).isQuiet); + assert(context.flags && INVALID_OPERATION); + dcm = "NaN"; + assert(abs(dcm).isQuiet); + assert(context.flags && INVALID_OPERATION); + dcm = "Inf"; + expd = "Inf"; + assert(abs(dcm) == expd); + dcm = "-Inf"; + expd = "Inf"; + assert(abs(dcm) == expd); + dcm = "0"; + expd = "0"; + assert(abs(dcm) == expd); + dcm = "-0"; + expd = "0"; + assert(abs(dcm) == expd); + dcm = "2.1"; + expd = "2.1"; + assert(abs(dcm) == expd); + dcm = -100; + expd = 100; + assert(abs(dcm) == expd); + dcm = 101.5; + expd = 101.5; + assert(abs(dcm) == expd); + dcm = -101.5; + assert(abs(dcm) == expd); + writeln("passed"); +} + +public Decimal nextPlus(const Decimal dcm) { + Decimal result; + if (isInvalidOperation(dcm, result)) { + return result; + } + if (dcm.isInfinite && dcm.sign) return -context.max(); + int adjx = dcm.expo + dcm.digits - context.precision; + if (adjx < context.eTiny) { + return Decimal(true, 0, context.eTiny); + } + Decimal addend = Decimal(1, adjx); + result = dcm + addend; + if (result > context.max) { + result = POS_INF; + } + return result; +} + +unittest { + write("next-plus...."); + pushContext(); + context.eMax = 999; + context.eMin = -999; + Decimal dcm; + Decimal expd; + dcm = 1; + expd = "1.00000001"; + assert(nextPlus(dcm) == expd); + dcm = 10; + expd = "10.0000001"; + assert(nextPlus(dcm) == expd); + dcm = 1E5; + expd = "100000.001"; + assert(nextPlus(dcm) == expd); + dcm = 1E8; + expd = "100000001"; + assert(nextPlus(dcm) == expd); + // num digits exceeds precision... + dcm = "1234567891"; + expd = "1.23456790E9"; + assert(nextPlus(dcm) == expd); + // result < tiny + dcm = "-1E-1007"; + expd = "-0E-1007"; + assert(nextPlus(dcm) == expd); + dcm = "-1.00000003"; + expd = "-1.00000002"; + assert(nextPlus(dcm) == expd); + dcm = "-Infinity"; + expd = "-9.99999999E+999"; + assert(nextPlus(dcm) == expd); + popContext(); + writeln("passed"); +} + +public Decimal nextMinus(const Decimal dcm) { + Decimal result; + if (isInvalidOperation(dcm, result)) { + return result; + } + if (dcm.isInfinite && !dcm.sign) return context.max(); + // This is necessary to catch the special case where ceff == 1 + Decimal red = reduce(dcm); + int adjx = red.expo + red.digits - context.precision; + if (dcm.ceff == 1) adjx--; + if (adjx < context.eTiny) { + return Decimal(false, 0, context.eTiny); + } + Decimal addend = Decimal(1, adjx); + result = dcm - addend; + if (result < -context.max) { + result = NEG_INF; + } + return result; +} + +unittest { + write("next-minus..."); + pushContext(); + context.eMax = 999; + context.eMin = -999; + Decimal dcm; + Decimal expd; + dcm = 1; + expd = "0.999999999"; + assert(nextMinus(dcm) == expd); + dcm = "1E-1007"; + expd = "0E-1007"; + assert(nextMinus(dcm) == expd); + dcm = "-1.00000003"; + expd = "-1.00000004"; + assert(nextMinus(dcm) == expd); + dcm = "Infinity"; + expd = "9.99999999E+999"; + assert(nextMinus(dcm) == expd); + popContext(); + writeln("passed"); +} + +public Decimal nextToward(const Decimal dcm1, const Decimal dcm2) { + Decimal result; + if (isInvalidOperation(dcm1, dcm2, result)) { + return result; + } + int comp = icompare(dcm1, dcm2); + if (comp < 0) return nextPlus(dcm1); + if (comp > 0) return nextMinus(dcm1); + result = copySign(dcm1, dcm2); + round(result); + return result; +} + +unittest { + write("next-toward.."); + DecimalContext save = context; + Decimal dcm1, dcm2; + Decimal expd; + dcm1 = 1; + dcm2 = 2; + expd = "1.00000001"; + assert(nextToward(dcm1,dcm2) == expd); + dcm1 = "-1E-1007"; + dcm2 = 1; + expd = "-0E-1007"; + assert(nextToward(dcm1,dcm2) == expd); + dcm1 = "-1.00000003"; + dcm2 = 0; + expd = "-1.00000002"; + assert(nextToward(dcm1,dcm2) == expd); + dcm1 = 1; + dcm2 = 0; + expd = "0.999999999"; + assert(nextToward(dcm1,dcm2) == expd); + dcm1 = "1E-1007"; + dcm2 = -100; + expd = "0E-1007"; + assert(nextToward(dcm1,dcm2) == expd); + dcm1 = "-1.00000003"; + dcm2 = -10; + expd = "-1.00000004"; + assert(nextToward(dcm1,dcm2) == expd); + dcm1 = "0.00"; + dcm2 = "-0.0000"; + expd = "-0.00"; + assert(nextToward(dcm1,dcm2) == expd); + context = save; + writeln("passed"); +} + +//-------------------------------- +// comparison functions +//-------------------------------- + +/// returns true if the numbers have the same exponent. +public bool sameQuantum(const Decimal x, const Decimal y) { + if (x.isNaN || y.isNaN) { + return x.isNaN && y.isNaN; + } + if (x.isInfinite || y.isInfinite) { + return x.isInfinite && y.isInfinite; + } + return x.expo == y.expo; +} + +unittest { + write("same-quantum."); + Decimal op1; + Decimal op2; + op1 = "2.17"; + op2 = "0.001"; + assert(!sameQuantum(op1, op2)); + op2 = "0.01"; + assert(sameQuantum(op1, op2)); + op2 = "0.1"; + assert(!sameQuantum(op1, op2)); + op2 = "1"; + assert(!sameQuantum(op1, op2)); + op1 = "Inf"; + op2 = "Inf"; + assert(sameQuantum(op1, op2)); + op1 = "NaN"; + op2 = "NaN"; + assert(sameQuantum(op1, op2)); + writeln("passed"); +} + +public Decimal compare(const Decimal dcm1, const Decimal dcm2, + const bool signal = false) { + // sNaN is an invalid operand + if (dcm1.isSignaling && dcm2.isSignaling) { + return invalidOp(); + } + // qNaN is invalid if signal flag is set. + if (signal && (dcm1.isNaN || dcm2.isNaN)) { + return invalidOp(); + } + // NaN returns > any number, including NaN + if (dcm1.isNaN) return ONE; + if (dcm2.isNaN) return -ONE; + + if (dcm1.sign != dcm2.sign) { + Decimal op1 = dcm1.sign ? -ONE : ONE; + Decimal op2 = -op1; + op1 = dcm1.isZero ? ZERO : op1; + op2 = dcm2.isZero ? ZERO : op2; + return op1 != op2 ? op1 : ZERO; + } + int diff = (dcm1.expo + dcm1.digits) - (dcm2.expo + dcm2.digits); + if (!dcm1.sign) { + if (diff > 0) return ONE; + if (diff < 0) return -ONE; + } + else { + if (diff > 0) return -ONE; + if (diff < 0) return ONE; + } + Decimal result = dcm1 - dcm2; + if (result.isZero) return ZERO; + return result.sign ? -ONE : ONE; +} + +public int icompare(const Decimal dcm1, const Decimal dcm2) { + // sNaN is invalid operand + // NaN returns > any number, including NaN + if (dcm1.isSignaling) { + invalidOp(); + return 1; + } + if (dcm2.isSignaling) { + invalidOp(); + return -1; + } + // NaN returns > any number, including NaN + if (dcm1.isNaN) return 1; + if (dcm2.isNaN) return -1; + + if (dcm1.sign != dcm2.sign) { + int op1 = dcm1.sign ? -1 : 1; + int op2 = -op1; + op1 = dcm1.isZero ? 0 : op1; + op2 = dcm2.isZero ? 0 : op2; + return op1 != op2 ? op1 : 0; + } + int diff = (dcm1.expo + dcm1.digits) - (dcm2.expo + dcm2.digits); + if (!dcm1.sign) { + if (diff > 0) return 1; + if (diff < 0) return -1; + } + else { + if (diff > 0) return -1; + if (diff < 0) return 1; + } + Decimal result = dcm1 - dcm2; + if (result.isZero) return 0; + return result.sign ? -1 : 1; +} + +unittest { + write("compare......"); + Decimal op1; + Decimal op2; + int result; + op1 = "2.1"; + op2 = "3"; + result = icompare(op1, op2); + assert(result == -1); + op1 = "2.1"; + op2 = "2.1"; + result = icompare(op1, op2); + assert(result == 0); + op1 = "2.1"; + op2 = "2.10"; + result = icompare(op1, op2); + assert(result == 0); + op1 = "3"; + op2 = "2.1"; + result = icompare(op1, op2); + assert(result == 1); + op1 = "2.1"; + op2 = "-3"; + result = icompare(op1, op2); + assert(result == 1); + op1 = "-3"; + op2 = "2.1"; + result = icompare(op1, op2); + assert(result == -1); + op1 = -3; + op2 = -4; + result = icompare(op1, op2); + assert(result == 1); + op1 = -300; + op2 = -4; + result = icompare(op1, op2); + assert(result == -1); + op1 = 3; + op2 = context.max; + result = icompare(op1, op2); + assert(result == -1); + op1 = -3; + op2 = -context.max; + result = icompare(op1, op2); + assert(result == 1); + + writeln("passed"); +} + +/// Returns 0 if the numbers are equal and have the same representation +public int compareTotal(const Decimal x, const Decimal y) { + if (x.sign != y.sign) { + return x.sign ? -1 : 1; + } + if (x.isQuiet || y.isQuiet) { + if (x.isQuiet && y.isQuiet) { + return 0; + } + return x.isQuiet ? 1 : -1; + } + if (x.isSignaling || y.isSignaling) { + return 0; + } + if (x.isInfinite || y.isInfinite) { + return 0; + } + int diff = (x.expo + x.digits) - (y.expo + y.digits); + if (diff > 0) return 1; + if (diff < 0) return -1; + Decimal result = x - y; + if (result.isZero) { + if (x.expo > y.expo) return 1; + if (x.expo < y.expo) return -1; + return 0; + } + return result.sign ? -1 : 1; +} + +unittest { + write("comp-total..."); + Decimal op1; + Decimal op2; + int result; + op1 = "12.73"; + op2 = "127.9"; + result = compareTotal(op1, op2); + assert(result == -1); + op1 = "-127"; + op2 = "12"; + result = compareTotal(op1, op2); + assert(result == -1); + op1 = "12.30"; + op2 = "12.3"; + result = compareTotal(op1, op2); + assert(result == -1); + op1 = "12.30"; + op2 = "12.30"; + result = compareTotal(op1, op2); + assert(result == 0); + op1 = "12.3"; + op2 = "12.300"; + result = compareTotal(op1, op2); + assert(result == 1); + op1 = "12.3"; + op2 = "NaN"; + result = compareTotal(op1, op2); + assert(result == -1); + writeln("passed"); +} + +int compareTotalMagnitude(const Decimal x, const Decimal y) { + return compareTotal(copyAbs(x), copyAbs(y)); +} + +// TODO: this is where the need for flags comes in. +/** + * Returns the maximum of the two operands (or NaN). + * If either is an sNaN, or both are quiet NaNs, a NaN is returned. + * Otherwise, Any (finite or infinite) number is larger than a NaN. + * If they are not numerically equal, the larger is returned. + * If they are numerically equal: + * 1) If the signs differ, the one with the positive sign is returned. + * 2) If they are positive, the one with the larger exponent is returned. + * 3) If they are negative, the one with the smaller exponent is returned. + * 4) Otherwise, they are indistinguishable; the first is returned. + */ +Decimal max(const Decimal op1, const Decimal op2) { + // if both are NaNs or either is an sNan, return NaN. + if (op1.isNaN && op2.isNaN || op1.isSignaling || op2.isSignaling) { + return NaN; + } + // if one op is a quiet NaN return the other + if (op1.isQuiet || op2.isQuiet) { + return (op1.isQuiet) ? op2 : op1; + } + // if the signs differ, return the unsigned operand + if (op1.sign != op2.sign) { + return op1.sign ? op2 : op1; + } + // if not numerically equal, return the larger + int comp = icompare(op1, op2); + if (comp != 0) { + return comp > 0 ? op1 : op2; + } + // if they have the same exponent they are identical, return either + if (op1.expo == op2.expo) { + return op1; + } + // if they are non-negative, return the one with larger exponent. + if (op1.sign == 0) { + return op1.expo > op2.expo ? op1 : op2; + } + // else they are negative; return the one with smaller exponent. + return op1.expo > op2.expo ? op2 : op1; +} + +unittest { + write("max.........."); + Decimal op1; + Decimal op2; + op1 = 3; + op2 = 2; + assert(max(op1, op2) == op1); + op1 = -10; + op2 = 3; + assert(max(op1, op2) == op2); + op1 = "1.0"; + op2 = "1"; + assert(max(op1, op2) == op2); + op1 = "7"; + op2 = "NaN"; + assert(max(op1, op2) == op1); + writeln("passed"); +} + +/** + * Returns the minimum of the two operands (or NaN). + * If either is an sNaN, or both are quiet NaNs, a NaN is returned. + * Otherwise, Any (finite or infinite) number is smaller than a NaN. + * If they are not numerically equal, the smaller is returned. + * If they are numerically equal: + * 1) If the signs differ, the one with the negative sign is returned. + * 2) If they are negative, the one with the larger exponent is returned. + * 3) If they are positive, the one with the smaller exponent is returned. + * 4) Otherwise, they are indistinguishable; the first is returned. + */ +Decimal min(const Decimal op1, const Decimal op2) { + // if both are NaNs or either is an sNan, return NaN. + if (op1.isNaN && op2.isNaN || op1.isSignaling || op2.isSignaling) { +/+ Decimal result; + result.flags = INVALID_OPERATION;+/ + return NaN; + } + // if one op is a quiet NaN return the other + if (op1.isQuiet || op2.isQuiet) { + return (op1.isQuiet) ? op2 : op1; + } + // if the signs differ, return the unsigned operand + if (op1.sign != op2.sign) { + return op1.sign ? op1 : op2; + } + // if not numerically equal, return the smaller + int comp = icompare(op1, op2); + if (comp != 0) { + return comp < 0 ? op1 : op2; + } + // if they have the same exponent they are identical, return either + if (op1.expo == op2.expo) { + return op1; + } + // if they are non-negative, return the one with smaller exponent. + if (op1.sign == 0) { + return op1.expo < op2.expo ? op1 : op2; + } + // else they are negative; return the one with larger exponent. + return op1.expo < op2.expo ? op2 : op1; +} + +unittest { + write("min.........."); + Decimal op1; + Decimal op2; + op1 = 3; + op2 = 2; + assert(min(op1, op2) == op2); + op1 = -10; + op2 = 3; + assert(min(op1, op2) == op1); + op1 = "1.0"; + op2 = "1"; + assert(min(op1, op2) == op1); + op1 = "7"; + op2 = "NaN"; + assert(min(op1, op2) == op1); + writeln("passed"); +} + +//------------------------------------------ +// +// Binary Arithmetic Operations +// +//------------------------------------------ + +/** + * Adds two Decimal numbers. + * + * This function corresponds to the "add and subtract" function + * in the General Decimal Arithmetic Specification and is the basis + * for the opAdd and opSub functions for the Decimal struct. + */ +Decimal add(const Decimal augend, const Decimal addend) { + + Decimal sum; + // check for NaN operand(s) + if (isInvalidOperation(augend, addend, sum)) { + return sum; + } + // if both operands are infinite + if (augend.isInfinite && addend.isInfinite) { + // (+inf) + (-inf) => invalid operation + if (augend.sign != addend.sign) { + return invalidOp(); + } + // both infinite with same sign + return augend; + } + +/+ if (isInvalidAddition(augend, addend, sum)) { + return sum; + }+/ +// TODO: is it okay to return the operand? is a copy implied? + // only augend is infinite, + if (augend.isInfinite) { + return augend; + } + // only addend is infinite + if (addend.isInfinite) { + return addend; + } + + // align the operands + alignOps(augend, addend); + + // add(0, 0) + if (augend.isZero && addend.isZero) { + sum = augend; + sum.sign = augend.sign && addend.sign; + return sum; + } + + // at this point, the result will be finite and not zero + // (before rounding) + sum.clear(); + + // if operands have the same sign... + if (augend.sign == addend.sign) { + sum.ceff = augend.ceff + addend.ceff; + sum.sign = augend.sign; + } + // ...else operands have different signs + else { + sum.ceff = augend.ceff - addend.ceff; + sum.sign = augend.sign; + if (sum.ceff < BIG_ZERO) { + sum.ceff = -sum.ceff; + sum.sign = !sum.sign; + } + } + // set the number of digits and the exponent + sum.digits = numDigits(sum.ceff, augend.digits); + sum.expo = augend.expo; + + // round the result + round(sum); + return sum; +} // end add(augend, addend) + +/** + * Subtracts two Decimal numbers. + * + * This function corresponds to the "add and subtract" function + * in the General Decimal Arithmetic Specification and is the basis + * for the opAdd and opSub functions for the Decimal struct. + */ +Decimal subtract(const Decimal minuend, const Decimal subtrahend) { + return add(minuend, copyNegate(subtrahend)); +} // end subtract(minuend, subtrahend) + +// TODO: these tests need to be cleaned up to rely less on strings +// and to check the NaN, Inf combinations better. +unittest { + write("add.........."); + Decimal dcm1 = Decimal("12"); + Decimal dcm2 = Decimal("7.00"); + Decimal sum = add(dcm1, dcm2); + assert(sum.toString() == "19.00"); + dcm1 = Decimal("1E+2"); + dcm2 = Decimal("1E+4"); + sum = add(dcm1, dcm2); + assert(sum.toString() == "1.01E+4"); + dcm1 = Decimal("1.3"); + dcm2 = Decimal("1.07"); + sum = subtract(dcm1, dcm2); + assert(sum.toString() == "0.23"); + dcm2 = Decimal("1.30"); + sum = subtract(dcm1, dcm2); + assert(sum.toString() == "0.00"); + dcm2 = Decimal("2.07"); + sum = subtract(dcm1, dcm2); + assert(sum.toString() == "-0.77"); + dcm1 = "Inf"; + dcm2 = 1; + sum = add(dcm1, dcm2); + assert(sum.toString() == "Infinity"); + dcm1 = "NaN"; + dcm2 = 1; + sum = add(dcm1, dcm2); + assert(sum.isQuiet); + dcm2 = "Infinity"; + sum = add(dcm1, dcm2); + assert(sum.isQuiet); + dcm1 = 1; + sum = subtract(dcm1, dcm2); + assert(sum.toString() == "-Infinity"); + dcm1 = "-0"; + dcm2 = 0; + sum = subtract(dcm1, dcm2); + assert(sum.toString() == "-0"); + writeln("passed"); +} + +Decimal multiply(const Decimal multiplier, const Decimal multiplicand) { + + Decimal product; + if (isInvalidMultiplication(multiplier, multiplicand, product)) { + return product; + } + if (multiplier.isInfinite || multiplicand.isInfinite) { + product = Decimal.infinity; + product.sign = multiplier.sign ^ multiplicand.sign; + return product; + } + product.clear(); + product.ceff = multiplier.ceff * multiplicand.ceff; + product.expo = multiplier.expo + multiplicand.expo; + product.sign = multiplier.sign ^ multiplicand.sign; + product.digits = numDigits(product.ceff, multiplier.digits + multiplicand.digits); + round(product); + return product; +} + +unittest { + write("multiply....."); + Decimal op1, op2, result; + op1 = Decimal("1.20"); + op2 = 3; + result = op1 * op2; + assert(result.toString() == "3.60"); + op1 = 7; + result = op1 * op2; + assert(result.toString() == "21"); + op1 = Decimal("0.9"); + op2 = Decimal("0.8"); + result = op1 * op2; + assert(result.toString() == "0.72"); + op1 = Decimal("0.9"); + op2 = Decimal("-0.0"); + result = op1 * op2; + assert(result.toString() == "-0.00"); + op1 = Decimal(654321); + op2 = Decimal(654321); + result = op1 * op2; + assert(result.toString() == "4.28135971E+11"); + op1 = -1; + op2 = "Infinity"; + result = op1 * op2; + assert(result.toString() == "-Infinity"); + op1 = -1; + op2 = 0; + result = op1 * op2; + assert(result.toString() == "-0"); + writeln("passed"); +} + +Decimal fma(const Decimal multiplier, const Decimal multiplicand, + const Decimal addend) { + Decimal product; + if (isInvalidMultiplication(multiplier, multiplicand, product)) { + return product; + } + product.clear(); + product.ceff = multiplier.ceff * multiplicand.ceff; + product.expo = multiplier.expo + multiplicand.expo; + product.sign = multiplier.sign ^ multiplicand.sign; + product.digits = numDigits(product.ceff, multiplier.digits + multiplicand.digits); + return add(product, addend); +} + +unittest { + write("fma.........."); + Decimal op1; + Decimal op2; + Decimal op3; + Decimal result; + op1 = 3; + op2 = 5; + op3 = 7; + result = (fma(op1, op2, op3)); + assert(result == Decimal(22)); + op1 = 3; + op2 = -5; + op3 = 7; + result = (fma(op1, op2, op3)); + assert(result == Decimal(-8)); + op1 = "888565290"; + op2 = "1557.96930"; + op3 = "-86087.7578"; + result = (fma(op1, op2, op3)); + assert(result == Decimal("1.38435736E+12")); + writeln("passed"); +} + +bool isZeroDividend(const Decimal dividend, const Decimal divisor, + Decimal quotient) { + if (dividend.isZero()) { + quotient.spval = SpVal.ZERO; + quotient.ceff = BIG_ZERO; + quotient.expo = 0; + quotient.digits = dividend.digits; + quotient.sign = dividend.sign; + return true; + } + return false; +} + +Decimal divide(const Decimal dividend, const Decimal divisor) { + + Decimal quotient; + if (isInvalidDivision(dividend, divisor, quotient)) { + return quotient; + } + if (isZeroDividend(dividend, divisor, quotient)) { + return quotient; + } + quotient.clear(); + + Decimal temp = dividend; + int adjust = 0; + while (temp.ceff < divisor.ceff) { + temp.ceff *= 10; + adjust++; + } + bool complete = false; + while (!complete) { + // repeated subtraction + while (divisor.ceff <= temp.ceff) { + temp.ceff -= divisor.ceff; + quotient.ceff++; + } + // check for done + if (temp.ceff == 0 && adjust >= 0 + || numDigits(quotient.ceff, 1) == context.precision + 2) { + complete = true; + } + else { + // bump the quotient and temp by 10 + quotient.ceff *= 10; + temp.ceff *= 10; + adjust++; + } + quotient.digits = numDigits(quotient.ceff, context.precision); + } + quotient.expo = temp.expo - divisor.expo - adjust; + quotient.sign = temp.sign ^ divisor.sign; + round(quotient); + return quotient; +} + +Decimal edivide(const Decimal dividend, const Decimal divisor) { + Decimal quotient; + if (isInvalidDivision(dividend, divisor, quotient)) { + return quotient; + } + if (isZeroDividend(dividend, divisor, quotient)) { + return quotient; + } + quotient.clear(); + Decimal denom = dividend; + Decimal numer = divisor; + // align operands + int diff = denom.expo - numer.expo; + if (diff < 0) { + numer.ceff *= pow10(-diff); + } + if (diff > 0) { + numer.ceff *= pow10(diff); + } + numer.digits = numDigits(numer.ceff, 1); + denom.digits = numDigits(denom.ceff, 1); + int shift = 2 + context.precision - denom.digits + numer.digits; + if (shift > 0) { + denom.ceff *= pow10(shift); + denom.expo -= shift; + } + quotient.ceff = denom.ceff / numer.ceff; + quotient.expo = denom.expo - numer.expo; + quotient.sign = denom.sign ^ numer.sign; + quotient.digits = numDigits(quotient.ceff, context.precision); + round(quotient); + return quotient; +} + +unittest { + write("divide......."); + Decimal dcm1, dcm2; + Decimal expd; + dcm1 = 1; + dcm2 = 3; + Decimal quotient = edivide(dcm1, dcm2); + expd = "0.333333333"; + assert(quotient == expd); + assert(quotient.toString() == expd.toString()); + dcm1 = 2; + dcm2 = 3; + quotient = edivide(dcm1, dcm2); + expd = "0.666666667"; + assert(quotient == expd); + dcm1 = 5; + dcm2 = 2; + quotient = divide(dcm1, dcm2); + expd = "2.5"; + assert(quotient == expd); + assert(quotient.toString() == expd.toString()); + dcm1 = 1; + dcm2 = 10; + expd = 0.1; + quotient = divide(dcm1, dcm2); + assert(quotient == expd); + assert(quotient.toString() == expd.toString()); + dcm1 = "8.00"; + dcm2 = 2; + expd = "4.00"; + quotient = divide(dcm1, dcm2); + assert(quotient == expd); + assert(quotient.toString() == expd.toString()); + dcm1 = "2.400"; + dcm2 = "2.0"; + expd = "1.20"; + quotient = divide(dcm1, dcm2); + assert(quotient == expd); + assert(quotient.toString() == expd.toString()); + dcm1 = 1000; + dcm2 = 100; + expd = 10; + quotient = divide(dcm1, dcm2); + assert(quotient == expd); + assert(quotient.toString() == expd.toString()); + dcm2 = 1; + quotient = divide(dcm1, dcm2); + expd = 1000; + assert(quotient == expd); + assert(quotient.toString() == expd.toString()); + dcm1 = "2.40E+6"; + dcm2 = 2; + expd = "1.20E+6"; + quotient = divide(dcm1, dcm2); + assert(quotient == expd); + assert(quotient.toString() == expd.toString()); + writeln("passed"); +} + +Decimal divideInteger(const Decimal dividend, const Decimal divisor) { + Decimal quotient; + if (isInvalidDivision(dividend, divisor, quotient)) { + return quotient; + } + if (isZeroDividend(dividend, divisor, quotient)) { + return quotient; + } + quotient.clear(); + Decimal denom = dividend; + Decimal numer = divisor; + // align operands + int diff = denom.expo - numer.expo; + if (diff < 0) { + numer.ceff *= pow10(-diff); + } + if (diff > 0) { + denom.ceff *= pow10(diff); + } + quotient.ceff = denom.ceff / numer.ceff; + quotient.expo = 0; + quotient.sign = denom.sign ^ numer.sign; + quotient.digits = numDigits(quotient.ceff, context.precision); + if (quotient.ceff == 0) quotient.spval = SpVal.ZERO; + return quotient; +} + +unittest { + write("div-int......"); + Decimal dividend; + Decimal divisor; + Decimal quotient; + Decimal expd; + dividend = 2; + divisor = 3; + quotient = divideInteger(dividend, divisor); + expd = 0; + assert(quotient == expd); + dividend = 10; + quotient = divideInteger(dividend, divisor); + expd = 3; + assert(quotient == expd); + dividend = 1; + divisor = "0.3"; + quotient = divideInteger(dividend, divisor); + assert(quotient == expd); + writeln("passed"); +} + +Decimal remainder(const Decimal dividend, const Decimal divisor) { + Decimal quotient; + if (isInvalidDivision(dividend, divisor, quotient)) { + return quotient; + } + if (isZeroDividend(dividend, divisor, quotient)) { + return quotient; + } + quotient = dividend - divisor * divideInteger(dividend, divisor); + return quotient; +} + +unittest { + write("remainder...."); + Decimal dividend; + Decimal divisor; + Decimal quotient; + Decimal expected; + dividend = "2.1"; + divisor = 3; + quotient = remainder(dividend, divisor); + expected = "2.1"; + assert(quotient == expected); + dividend = 10; + quotient = remainder(dividend, divisor); + expected = 1; + assert(quotient == expected); + dividend = -10; + quotient = remainder(dividend, divisor); + expected = -1; + assert(quotient == expected); + dividend = 10.2; + divisor = 1; + quotient = remainder(dividend, divisor); + expected = "0.2"; + assert(quotient == expected); + dividend = 10; + divisor = 0.3; + quotient = remainder(dividend, divisor); + expected = "0.1"; + assert(quotient == expected); + dividend = 3.6; + divisor = 1.3; + quotient = remainder(dividend, divisor); + expected = "1.0"; + assert(quotient == expected); + writeln("passed"); +} + +//-------------------------------- +// rounding +//-------------------------------- + +public Decimal rint(const Decimal dec){ + if (dec.isSignaling) return invalidOp(); + if (dec.isSpecial) return dec; + if (dec.expo >= 0) return dec; + pushContext(); + context.precision = dec.digits; + Decimal result = quantize(dec, ONE); + popContext(); + return result; +} + +public Decimal nearbyint(const Decimal dec){ + // this operation shouldn't affect the inexact or rounded flags + // so we'll save them in case they were already set. + bool inexact = context.getFlag(INEXACT); + bool rounded = context.getFlag(ROUNDED); + Decimal result = rint(dec); + context.setFlag(INEXACT, inexact); + context.setFlag(ROUNDED, rounded); + return result; +} + +unittest { + write("rnd-int-ex..."); + Decimal dec; + Decimal expd; + Decimal actual; + dec = 2.1; + expd = 2; + actual = rint(dec); + assert(actual == expd); + dec = 100; + expd = 100; + assert(rint(dec) == expd); + assert(rint(dec).toString() == expd.toString()); + dec = "100.0"; + assert(rint(dec) == expd); + assert(rint(dec).toString() == expd.toString()); + dec = "101.5"; + expd = 102; + assert(rint(dec) == expd); + assert(rint(dec).toString() == expd.toString()); + dec = "-101.5"; + expd = -102; + assert(rint(dec) == expd); + assert(rint(dec).toString() == expd.toString()); + dec = "10E+5"; + expd = "1.0E+6"; + assert(rint(dec) == expd); + assert(rint(dec).toString() == expd.toString()); + dec = "7.89E+77"; + expd = "7.89E+77"; + assert(rint(dec) == expd); + assert(rint(dec).toString() == expd.toString()); + dec = "-Inf"; + expd = "-Infinity"; + assert(rint(dec) == expd); + assert(rint(dec).toString() == expd.toString()); + writeln("passed"); +} + +/** + * Clips the coefficient of the number to the specified precision. + * Returns the remainder for adjustments based on rounding mode. + * Sets the ROUNDED and INEXACT flags. + */ +private BigInt shorten(ref Decimal number) { + BigInt remainder = BIG_ZERO; + int diff = number.digits - context.precision; + if (diff <= 0) { + return remainder; + } + context.setFlag(ROUNDED); + if (context.precision == 0) { + remainder = number.ceff; + number.ceff = 0; + number.digits = 1; + } + else { + BigInt divisor = pow10(diff); + remainder = number.ceff % divisor; + number.ceff /= divisor; + number.digits = context.precision; + number.expo += diff; + } + if (remainder != BIG_ZERO) { + context.setFlag(INEXACT); + } + return remainder; +} + +/** + * Increments the coefficient by 1. If this causes an overflow, divides by 10. + */ +private void increment(ref BigInt number) { + number++; + if (lastDigit(number) == 0) { + number /= 10; + } +} + +// TODO: need to signal inexact and rounded. +private void roundByMode(ref Decimal number) { + BigInt remainder = shorten(number); + + // if the rounded flag is not set by the shorten operation, return + if (!context.getFlag(ROUNDED)) { + return; + } + // if the remainder is zero, return + if (remainder == BIG_ZERO) { + return; + } + + switch (context.mode) { + case Rounding.DOWN: + return; + case Rounding.HALF_UP: + if (firstDigit(remainder) >= 5) { + increment(number.ceff); + } + return; + case Rounding.HALF_EVEN: + BigInt test = 5 * pow10(numDigits(remainder,1)-1); + int result = remainder.opCmp(test); + if (result > 0) { + increment(number.ceff); + return; + } + if (result < 0) { + return; + } + // if last digit is odd... + if (lastDigit(number.ceff) & 1 == 1) { + increment(number.ceff); + } + return; + case Rounding.CEILING: + if (!number.sign && remainder != BIG_ZERO) { + increment(number.ceff); + } + return; + case Rounding.FLOOR: + if (number.sign && remainder != BIG_ZERO) { + increment(number.ceff); + } + return; + case Rounding.HALF_DOWN: + if (firstDigit(remainder) > 5) { + increment(number.ceff); + } + return; + case Rounding.UP: + if (remainder != BIG_ZERO) { + increment(number.ceff); + } + return; + } // end switch(mode) +} // end roundByMode() + +public void round(ref Decimal number) { + if (!number.isFinite) return; + + context.clearFlags(); + // check for subnormal + bool subnormal = false; + if (number.isSubnormal()) { + context.setFlag(SUBNORMAL); + subnormal = true; + } + + // check for overflow + if (number.overflow()) { + context.setFlag(OVERFLOW); + switch (context.mode) { + case Rounding.HALF_UP: + case Rounding.HALF_EVEN: + case Rounding.HALF_DOWN: + case Rounding.UP: + bool sign = number.sign; + number = POS_INF; + number.sign = sign; + break; + case Rounding.DOWN: + bool sign = number.sign; + number = context.max; + number.sign = sign; + break; + case Rounding.CEILING: + if (number.sign) { + number = context.max; + number.sign = true; + } + else { + number = POS_INF; + } + break; + case Rounding.FLOOR: + if (number.sign) { + number = NEG_INF; + } else { + number = context.max; + } + break; + } + context.setFlag(INEXACT); + context.setFlag(ROUNDED); + return; + } + roundByMode(number); + // check for underflow + if (number.isSubnormal /+&& number.isInexact+/) { + context.setFlag(SUBNORMAL); + int diff = context.eTiny - number.adjustedExponent(); + if (diff > number.digits) { + number.ceff = 0; + number.expo = context.eTiny; + } else if (diff > 0) { + writeln("We got a tiny one!"); + } + } + // check for zero + if (number.spval == SpVal.CLEAR && number.ceff == BIG_ZERO) { + number.spval = SpVal.ZERO; + // subnormal rounding to zero == clamped + // Spec. p. 51 + if (subnormal) { + context.setFlag(CLAMPED); + } + return; + } +} // end round() + +unittest { + write("round........"); + Decimal before = Decimal(1234567890); + Decimal after = before; + pushContext(); + context.precision = 3; + round(after); + assert(after.toString() == "1.23E+9"); + after = before; + context.precision = 4; + round(after); + assert(after.toString() == "1.235E+9"); + after = before; + context.precision = 5; + round(after); + assert(after.toString() == "1.2346E+9"); + after = before; + context.precision = 6; + round(after); + assert(after.toString() == "1.23457E+9"); + after = before; + context.precision = 7; + round(after); + assert(after.toString() == "1.234568E+9"); + after = before; + context.precision = 8; + round(after); + assert(after.toString() == "1.2345679E+9"); + before = "1235"; + after = before; + context.precision = 3; + round(after); + assert(after.toAbstract() == "[0,124,1]"); + before = "12359"; + after = before; + context.precision = 3; + round(after); + assert(after.toAbstract() == "[0,124,2]"); + before = "1245"; + after = before; + context.precision = 3; + round(after); + assert(after.toAbstract() == "[0,124,1]"); + before = "12459"; + after = before; + context.precision = 3; + round(after); + assert(after.toAbstract() == "[0,125,2]"); + popContext(); + writeln("passed"); +} + +public void setDigits(ref Decimal number) { + int diff = number.digits - context.precision; + if (diff > 0) { + round(number); + } + else if (diff < 0) { + number.ceff *= pow10(-diff); + number.expo += diff; + } + number.digits = context.precision; +} + +/** + * Returns the number which is equal in value and sign + * to the first operand and which has its exponent set + * to be equal to the exponent of the second operand. + */ + // TODO: this has unusual flag rules +Decimal quantize(const Decimal dcm1, const Decimal dcm2) { + Decimal result; + if (isInvalidOperation(dcm1, dcm2, result)) { + return result; + } + if (dcm1.isInfinite != dcm2.isInfinite() || + dcm2.isInfinite != dcm1.isInfinite()) { + return invalidOp(); + } + if (dcm1.isInfinite() && dcm2.isInfinite()) { + return dcm1.dup; + } + result = dcm1; + int diff = dcm1.expo - dcm2.expo; + if (diff == 0) { + return result; + } + if (diff > 0) { + result.ceff *= pow10(diff); + result.digits += diff; + result.expo = dcm2.expo; + if (result.digits > context.precision) { + result = NaN; + } + return result; + } + else { + pushContext(); + context.precision = + -diff > dcm1.digits ? 0 : dcm1.digits + diff; + round(result); + result.expo = dcm2.expo; + popContext(); + return result; + } +} + +unittest { + write("quantize....."); + Decimal op1; + Decimal op2; + Decimal result; + Decimal expd; + string str; + op1 = "2.17"; + op2 = "0.001"; + expd = "2.170"; + result = quantize(op1, op2); + assert(result == expd); + op1 = "2.17"; + op2 = "0.01"; + expd = "2.17"; + result = quantize(op1, op2); + assert(result == expd); + op1 = "2.17"; + op2 = "0.1"; + expd = "2.2"; + result = quantize(op1, op2); + assert(result == expd); + op1 = "2.17"; + op2 = "1e+0"; + expd = "2"; + result = quantize(op1, op2); + assert(result == expd); + op1 = "2.17"; + op2 = "1e+1"; + expd = "0E+1"; + result = quantize(op1, op2); + assert(result.toString() == expd.toString()); + op1 = "-Inf"; + op2 = "Infinity"; + expd = "-Infinity"; + result = quantize(op1, op2); + assert(result == expd); + op1 = "2"; + op2 = "Infinity"; + expd = "NaN"; + result = quantize(op1, op2); + assert(result.toString() == expd.toString()); + op1 = "-0.1"; + op2 = "1"; + expd = "-0"; + result = quantize(op1, op2); + assert(result.toString() == expd.toString()); + op1 = "-0"; + op2 = "1e+5"; + expd = "-0E+5"; + result = quantize(op1, op2); + assert(result.toString() == expd.toString()); + op1 = "+35236450.6"; + op2 = "1e-2"; + expd = "NaN"; + result = quantize(op1, op2); + assert(result.toString() == expd.toString()); + op1 = "-35236450.6"; + op2 = "1e-2"; + expd = "NaN"; + result = quantize(op1, op2); + assert(result.toString() == expd.toString()); + op1 = "217"; + op2 = "1e-1"; + expd = "217.0"; + result = quantize(op1, op2); + assert(result.toString() == expd.toString()); + op1 = "217"; + op2 = "1e+0"; + expd = "217"; + result = quantize(op1, op2); + assert(result.toString() == expd.toString()); + op1 = "217"; + op2 = "1e+1"; + expd = "2.2E+2"; + result = quantize(op1, op2); + assert(result.toString() == expd.toString()); + op1 = "217"; + op2 = "1e+2"; + expd = "2E+2"; + result = quantize(op1, op2); + assert(result.toString() == expd.toString()); + assert(result == expd); + writeln("passed"); +} + +/** + * Reduces operand to simplest form. All trailing zeros are removed. + */ + // TODO: has non-standard flag setting +Decimal reduce(const Decimal dcm) { + Decimal result; + if (isInvalidOperation(dcm, result)) { + return result; + } + result = dcm; + if (!result.isFinite()) { + return result; + } + BigInt temp = result.ceff % 10; + while (result.ceff != 0 && temp == 0) { + result.expo++; + result.ceff = result.ceff / 10; + temp = result.ceff % 10; + } + if (result.ceff == 0) { + result.spval = SpVal.ZERO; + result.expo = 0; + } + result.digits = numDigits(result.ceff); + return result; +} + +unittest { + write("reduce......."); + Decimal dec; + Decimal red; + string str; + dec = "2.1"; + str = "2.1"; + red = reduce(dec); + assert(red.toString() == str); + dec = "-2.0"; + str = "-2"; + red = reduce(dec); + assert(red.toString() == str); + dec = "1.200"; + str = "1.2"; + red = reduce(dec); + assert(red.toString() == str); + dec = "-120"; + str = "-1.2E+2"; + red = reduce(dec); + assert(red.toString() == str); + dec = "120.00"; + str = "1.2E+2"; + red = reduce(dec); + assert(red.toString() == str); + writeln("passed"); +} + +private: + +/** + * Sets the invalid-operation flag and + * returns a quiet NaN. + */ +Decimal invalidOp() { + context.flags |= INVALID_OPERATION; + return NaN; +} + +/** + * aligns the two operands by raising the smaller exponent + * to the value of the larger exponent, and adjusting the + * coefficient so the value remains the same. + */ +void alignOps(ref Decimal op1, ref Decimal op2) { + int diff = op1.expo - op2.expo; + if (diff > 0) { + op1.ceff *= pow10(diff); + op1.expo = op2.expo; + } + else if (diff < 0) { + op2.ceff *= pow10(-diff); + op2.expo = op1.expo; + } +} + +/* + * "The result of any arithmetic operation which has an operand + * which is a NaN (a quiet NaN or a signaling NaN) is [s,qNaN] + * or [s,qNaN,d]. The sign and any diagnostic information is copied + * from the first operand which is a signaling NaN, or if neither is + * signaling then from the first operand which is a NaN." + * -- General Decimal Arithmetic Specification, p. 24 + */ +bool isInvalidOperation(const Decimal dcm1, const Decimal dcm2, + ref Decimal result) { + // if either operand is an sNaN... + if (dcm1.isSignaling || dcm2.isSignaling) { + // set the result to the first sNaN operand + result = dcm1.isSignaling ? dcm1 : dcm2; + // retain sign and payload; convert to qNaN + result.spval = SpVal.QNAN; + // flag the invalid operation + context.flags |= INVALID_OPERATION; + return true; + } + // ...else if either operand is a qNaN... + if (dcm1.isQuiet || dcm2.isQuiet) { + // set the result to the first qNaN operand + result = dcm1.isQuiet ? dcm1 : dcm2; + // flag the invalid operation + context.flags |= INVALID_OPERATION; + return true; + } + // ...otherwise, no flags are set and result is unchanged + return false; +} + +unittest { + write("invalid......"); + Decimal dcm; + Decimal expd; + Decimal actual; + + dcm = "sNaN123"; + expd = "NaN123"; + actual = abs(dcm); + assert(actual.isQuiet); + assert(context.flags && INVALID_OPERATION); + assert(actual.toAbstract == expd.toAbstract); + dcm = "NaN123"; + actual = abs(dcm); + assert(actual.isQuiet); + assert(context.flags && INVALID_OPERATION); + assert(actual.toAbstract == expd.toAbstract); + + dcm = "sNaN123"; + expd = "NaN123"; + actual = -dcm; + assert(actual.isQuiet); + assert(context.flags && INVALID_OPERATION); + assert(actual.toAbstract == expd.toAbstract); + dcm = "NaN123"; + actual = -dcm; + assert(actual.isQuiet); + assert(context.flags && INVALID_OPERATION); + assert(actual.toAbstract == expd.toAbstract); + writeln("passed"); +} + +/* + * "The result of any arithmetic operation which has an operand + * which is a NaN (a quiet NaN or a signaling NaN) is [s,qNaN] + * or [s,qNaN,d]. The sign and any diagnostic information is copied + * from the first operand which is a signaling NaN, or if neither is + * signaling then from the first operand which is a NaN." + * -- General Decimal Arithmetic Specification, p. 24 + */ +bool isInvalidOperation(const Decimal dcm, ref Decimal result) { + // if the operand is an sNaN... + if (dcm.isSignaling) { + // set the result to the sNaN operand + result = dcm; + // retain sign and payload; convert to qNaN + result.spval = SpVal.QNAN; + // flag the invalid operation + context.flags |= INVALID_OPERATION; + return true; + } + // ...else if the operand is a qNaN... + if (dcm.isQuiet) { + // set the result to the qNaN operand + result = dcm; + // flag the invalid operation + context.flags |= INVALID_OPERATION; + return true; + } + // ...otherwise, no flags are set and result is unchanged + return false; +} + +// TODO: add unit tests here for operations that apply +unittest { + +} + +/+/* + * -- General Decimal Arithmetic Specification, p. 52, "Invalid operation" + */ +bool isInvalidAddition(Decimal op1, Decimal op2, ref Decimal result) { + if (isInvalidOperation(op1, op2, result)) { + return true; + } + // if both operands are infinite + if (op1.isInfinite && op2.isInfinite) { + // (+inf) + (-inf) => invalid operation + if (op1.sign != op2.sign) { + result = invalidOp(); + return true; + } + } + return false; +}+/ + +/* + * -- General Decimal Arithmetic Specification, p. 52, "Invalid operation" + */ +bool isInvalidMultiplication(Decimal op1, Decimal op2, ref Decimal result) { + if (isInvalidOperation(op1, op2, result)) { + return true; + } + if (op1.isZero && op2.isInfinite || op1.isInfinite && op2.isZero) { + result = NaN; + return true; + } + return false; +} + +/* + * -- General Decimal Arithmetic Specification, p. 52, "Invalid operation" + */ +bool isInvalidDivision(Decimal dividend, Decimal divisor, ref Decimal quotient) { + if (isInvalidOperation(dividend, divisor, quotient)) { + return true; + } + if (divisor.isZero()) { + if (dividend.isZero()) { + quotient = invalidOp(); + } + else { + quotient.spval = SpVal.INF; + context.flags |= DIVISION_BY_ZERO; + quotient.ceff = BIG_ZERO; + quotient.sign = dividend.sign ^ divisor.sign; + } + return true; + } + return false; +} + +//-------------------------------- +// unit tests +//-------------------------------- + +//-------------------------------- +// additions to BigInt +//-------------------------------- + +private: + static immutable BigInt BIG_ZERO = { [0] }; + static immutable BigInt BIG_ONE = { [1] }; + static immutable BigInt BIG_FIVE = { [5] }; + static immutable BigInt BIG_TEN = { [10] }; + +/** + * Returns the number of decimal digits in BigInt value. + * There are probably significant efficiency gains available. + **/ +uint numDigits(const BigInt big, int n = 1) { + if (big == 0) return 1; + if (n <= 0) n = 1; + int m = n; + while (big < pow10(m-1)) m--; + if (m != n) return m; + while (big >= pow10(m)) m++; + return m; +} + +/** + * Returns a BigInt with the value of 10 raised to the specified power. + * There are probably significant efficiency gains available. + **/ +public BigInt pow10(uint power) { + static immutable int BILLION = 1000000000; + static immutable int array[10] = + [ 1, 10, 100, 1000, 10000, 100000, 1000000, + 10000000, 100000000, BILLION ]; + + if (power < 10) { + return BigInt(array[power]); + } + BigInt big = BigInt(BILLION); + power -= 9; + int quo = power / 9; + int rem = power % 9; + for (int i = 0; i < quo; i++) { + big *= BILLION; + } + return big * array[rem]; +} + +/** + * Returns the first decimal digit of the specified BigInt. + */ +uint firstDigit(BigInt big) { + uint digits = numDigits(big, 1); + if (digits == 0) { + return 0; + } + BigInt bfd = big / pow10(digits - 1); + uint ifd; + bfd.castTo(ifd); + return ifd; +} + +/** + * Returns the last decimal digit of the specified BigInt. + */ +uint lastDigit(BigInt big) { + return big % 10; +} + +unittest { + write("bigint......."); + string str = "1"; + BigInt big = pow10(0); + assert(str == big.toString); + for (int i = 1; i < 35; i++) { + str ~= "0"; + big = pow10(i); + assert(str == big.toString); + assert(numDigits(big) == str.length); + } + writeln("passed"); +} + +public void main() { + writeln("Hello, world!"); +/+ writeln("eTiny = ", context.eTiny); + writeln("tiny min = ", Decimal(1, context.eTiny)); + writeln("tiny min = ", Decimal(1, context.eTiny - 1)); + writeln("max = ", context.max()); + writeln("max1 = ", Decimal(999999999, 99)); + writeln("dig = ", context.dig()); + writeln("eps = ", context.epsilon()); + writeln("smallest = ", context.min_normal()*context.epsilon()); + writeln("1/epsilon = ", Decimal(1)/context.epsilon()); + writeln("max * min = ", context.max * context.min_normal); + writeln("mant_dig = ", context.mant_dig); + writeln("min_exp = ", context.min_exp); + writeln("max_exp = ", context.max_exp);+/ + + + // TODO: this next one goes crazy -- shows need for checks on construction + // TODO: turns out this is being converted to a double (or real) and + // then it's really weird. +// writeln("bigger = ", Decimal(999999999999)); +// float f = float.max; + // TODO: convert these to assserts +/+ writeln("f.max = ", f); + writeln("f.min = ", float.min); + writeln("f = ", 2 * float.min); + writeln("d.max = ", Decimal.max()); + writeln("d.min = ", Decimal.min_normal()); + writeln("2 * d.min = ", 2 * Decimal.min_normal()); + writeln("0.1 * d.min = ", 0.1 * Decimal.min_normal());+/ + // TODO: move this to unittesting +/+ Decimal dec = Decimal(PI); + writeln("pi = ", dec ); + dec = Decimal(PI, 19); + writeln("pi = ", dec ); + dec = Decimal(1.3, 25); + writeln("pi = ", dec ); + dec = Decimal(0.1, 25); + writeln("pi = ", dec ); + dec = Decimal(2.0, 25); + writeln("pi = ", dec ); + dec = Decimal(200.5, 25); + writeln("pi = ", dec ); + writeln(double.dig); + writeln(real.dig);+/ +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/decimal/math.d Sat Mar 13 13:22:25 2010 -0800 @@ -0,0 +1,526 @@ +module decimal.math; + +import decimal.decimal; +import decimal.context; + +public: + + //-------------------------------- + // + // CONSTANTS + // + //-------------------------------- + + /** + * Returns the value of e to the default precision. + */ + Decimal e() { + Decimal result; + return result; + } + + /** + * Returns the value of e to the specified precision. + */ + Decimal e(uint precision) { + Decimal result; + return result; + } + + /** + * Returns the value of pi to the default precision. + */ + Decimal pi() { + Decimal result; + return result; + } + + /** + * Returns the value of pi to the specified precision. + */ + Decimal pi(uint precision) {; + Decimal result; + return result; + } + + /** + * Returns the square root of the argument to the default precision. + */ + Decimal sqrt(Decimal arg) { + Decimal result; + return result; + } + + /** + * Returns the square root of the argument to the specified precision. + */ + Decimal sqrt(Decimal arg, uint precision) { + Decimal result; + return result; + } + + //-------------------------------- + // + // EXPONENTIAL AND LOGARITHMIC FUNCTIONS + // + //-------------------------------- + + /** + * Decimal version of std.math function. + * Required by General Decimal Arithmetic Specification + * + */ + Decimal exp(Decimal arg) { + Decimal result; + return result; + } + + /** + * Decimal version of std.math function. + * 2^x + */ + Decimal exp2(Decimal arg) { + Decimal result; + return result; + } + + /** + * Decimal version of std.math function. + * exp(x) - 1 + */ + Decimal expm1(Decimal arg) { + Decimal result; + return result; + } + + /** + * Decimal version of std.math function. + * Required by General Decimal Arithmetic Specification + * + */ + Decimal log(Decimal arg) { + Decimal result; + return result; + } + + /** + * Decimal version of std.math function. + * log(1 + x) + */ + Decimal log1p(Decimal arg) { + Decimal result; + return result; + } + + /** + * Decimal version of std.math function. + * Required by General Decimal Arithmetic Specification + * + */ + Decimal log10(Decimal arg) { + Decimal result; + return result; + } + + /** + * Decimal version of std.math function. + * Required by General Decimal Arithmetic Specification + * + */ + Decimal log2(Decimal arg) { + Decimal result; + return result; + } + + /** + * Decimal version of std.math function. + * Required by General Decimal Arithmetic Specification + * + */ + Decimal pow(Decimal op1, Decimal op2) { + Decimal result; + return result; + } + + //-------------------------------- + // + // TRIGONOMETRIC FUNCTIONS + // + //-------------------------------- + + /** + * Decimal version of std.math function. + * + */ + Decimal sin(Decimal arg) { + Decimal result; + return result; + } + + /** + * Decimal version of std.math function. + * + */ + Decimal cos(Decimal arg) { + Decimal result; + return result; + } + + /** + * Replaces std.math function expi + * + */ + Decimal[] sincos(Decimal arg) { + Decimal[] result; + return result; + } + + /** + * Decimal version of std.math function. + * + */ + Decimal tan(Decimal arg) { + Decimal result; + return result; + } + + /** + * Decimal version of std.math function. + * + */ + Decimal asin(Decimal arg) { + Decimal result; + return result; + } + + /** + * Decimal version of std.math function. + * + */ + Decimal acos(Decimal arg) { + Decimal result; + return result; + } + + /** + * Decimal version of std.math function. + * + */ + Decimal atan(Decimal arg) { + Decimal result; + return result; + } + + /** + * Decimal version of std.math function. + * + */ + Decimal atan2(Decimal y, Decimal x) { + Decimal result; + return result; + } + + //-------------------------------- + // + // HYPERBOLIC TRIGONOMETRIC FUNCTIONS + // + //-------------------------------- + + /** + * Decimal version of std.math function. + * + */ + Decimal sinh(Decimal arg) { + Decimal result; + return result; + } + + /** + * Decimal version of std.math function. + * + */ + Decimal cosh(Decimal arg) { + Decimal result; + return result; + } + + /** + * Decimal version of std.math function. + * + */ + Decimal tanh(Decimal arg) { + Decimal result; + return result; + } + + /** + * Decimal version of std.math function. + * + */ + Decimal asinh(Decimal arg) { + Decimal result; + return result; + } + + /** + * Decimal version of std.math function. + * + */ + Decimal acosh(Decimal arg) { + Decimal result; + return result; + } + + /** + * Decimal version of std.math function. + * + */ + Decimal atanh(Decimal arg) { + Decimal result; + return result; + } + + //-------------------------------- + // + // General Decimal Arithmetic Specification Functions + // + //-------------------------------- + + /** + * part of spec + * + * TODO: implement + */ + Decimal compare(Decimal op1, Decimal op2) { + Decimal result; + return result; + } + + /** + * part of spec + * + * TODO: implement + */ + Decimal compareSignal(Decimal op1, Decimal op2) { + Decimal result; + return result; + } + + /** + * part of spec + * + * TODO: implement + */ + Decimal compareTotal(Decimal op1, Decimal op2) { + Decimal result; + return result; + } + + /** + * part of spec + * + * TODO: implement + */ + Decimal divideInteger(Decimal op1, Decimal op2) { + Decimal result; + return result; + } + + /** + * part of spec + * + * TODO: implement + */ + Decimal fma(Decimal op1, Decimal op2, Decimal op3) { + Decimal result; + return result; + } + + /** + * part of spec + * + * TODO: implement + */ + Decimal ln(Decimal op1) { + Decimal result; + return result; + } + + /** + * part of spec + * + * TODO: implement + */ + Decimal log10(Decimal op1) { + Decimal result; + return result; + } + + /** + * part of spec + * + * TODO: implement + */ + Decimal max(Decimal op1, Decimal op2) { + Decimal result; + return result; + } + + /** + * part of spec + * + * TODO: implement + */ + Decimal maxMagnitude(Decimal op1, Decimal op2) { + Decimal result; + return result; + } + + /** + * part of spec + * + * TODO: implement + */ + Decimal min(Decimal op1, Decimal op2) { + Decimal result; + return result; + } + + /** + * part of spec + * + * TODO: implement + */ + Decimal minMagnitude(Decimal op1, Decimal op2) { + Decimal result; + return result; + } + + /** + * part of spec + * + * TODO: implement + */ + Decimal nextMinus(Decimal op1) { + Decimal result; + return result; + } + + /** + * part of spec + * + * TODO: implement + */ + Decimal nextPlus(Decimal op1) { + Decimal result; + return result; + } + + /** + * part of spec + * + * TODO: implement + */ + Decimal nextToward(Decimal op1, Decimal op2) { + Decimal result; + return result; + } + + /** + * part of spec + * + * TODO: implement + */ + Decimal power(Decimal op1, Decimal op2) { + Decimal result; + return result; + } + + /** + * part of spec + * + * TODO: implement + */ + Decimal remainder(Decimal op1, Decimal op2) { + Decimal result; + return result; + } + + /** + * part of spec + * + * TODO: implement + */ + Decimal remainderNear(Decimal op1, Decimal op2) { + Decimal result; + return result; + } + + /** + * part of spec + * + * NOTE: performs both round-to-integral-exact and + * round-to-integral-value + * + * TODO: implement + */ + Decimal rint(Decimal op1, Decimal op2) { + Decimal result; + return result; + } + +// logical operations + + /** + * part of spec + * + * TODO: implement + */ + Decimal and(Decimal op1, Decimal op2) { + Decimal result; + return result; + } + + /** + * part of spec + * + * TODO: implement + */ + Decimal or(Decimal op1, Decimal op2) { + Decimal result; + return result; + } + + /** + * part of spec + * + * TODO: implement + */ + Decimal xor(Decimal op1, Decimal op2) { + Decimal result; + return result; + } + + /** + * part of spec + * + * TODO: implement + */ + Decimal invert(Decimal op1) { + Decimal result; + return result; + } + + + /** + * part of spec + * + * TODO: implement + */ + Decimal compareTotal(Decimal op1, Decimal op2) { + Decimal result; + return result; + } + + +