Mercurial > projects > decimal
view src/decimal/decimal.d @ 3:bfda0b347c07
Initial development of BCD integers
author | Paul (paul.d.anderson@comcast.net) |
---|---|
date | Thu, 18 Mar 2010 18:09:20 -0700 |
parents | a984d3056cc4 |
children |
line wrap: on
line source
/** * A D programming language implementation of the * General Decimal Arithmetic Specification, * Version 1.70, (25 March 2009). * * by Paul D. Anderson * * Boost Software License - Version 1.0 - August 17th, 2003 * * Permission is hereby granted, free of charge, to any person or organization * obtaining a copy of the software and accompanying documentation covered by * this license (the "Software") to use, reproduce, display, distribute, * execute, and transmit the Software, and to prepare derivative works of the * Software, and to permit third-parties to whom the Software is furnished to * do so, all subject to the following: * * The copyright notices in the Software and this entire statement, including * the above license grant, this restriction and the following disclaimer, * must be included in all copies of the Software, in whole or in part, and * all derivative works of the Software, unless such copies or derivative * works are solely in the form of machine-executable object code generated by * a source language processor. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. **/ // 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; } // writeln("aligning"); // 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) { // writeln("same sign"); // writeln("augend = ", augend.ceff); // writeln("addend = ", addend.ceff); sum.ceff = augend.ceff + addend.ceff; // writeln("sum = ", sum.ceff); sum.sign = augend.sign; // writeln("set sign"); } // ...else operands have different signs else { // writeln("signs differ"); 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; // writeln("rounding"); // round the result round(sum); // writeln("rounded"); 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"); }