# HG changeset patch # User Paul (paul.d.anderson@comcast.net) # Date 1269494498 25200 # Node ID 48d564218e05853593f0a74c91dbc8a8c5cf1fd6 # Parent c991b9fde45cc095e6bb6be62b4d737c9311bbb9 Added rounding, additional unit tests, strip leading and trailing zeros. diff -r c991b9fde45c -r 48d564218e05 src/decimal/bcd.d --- a/src/decimal/bcd.d Tue Mar 23 21:54:23 2010 -0700 +++ b/src/decimal/bcd.d Wed Mar 24 22:21:38 2010 -0700 @@ -218,7 +218,7 @@ * the remainder is clipped. */ void setNumDigits(uint n) { - this = stripLeadingZeros(this); + stripl(this); if (n > digits.length) { digits.length = n; } @@ -343,17 +343,41 @@ * this BCD integer has leading zeros. */ const bool hasLeadingZeros() { - return numDigits > 0 && firstDigit == 0; + return numDigits > 1 && firstDigit == 0; + } + + unittest { + write("hasLeadingZeros..."); + assert(!ZERO.hasLeadingZeros); + Bcd b = Bcd(0); + assert(!b.hasLeadingZeros); + b = "-00000001"; + assert(b.hasLeadingZeros); + b = "-00000000"; + assert(b.hasLeadingZeros); + writeln("passed"); } /** * Returns true if the value of this BCD integer is zero. - * Ignores and leading zeros and the sign of the number. + * Ignores leading zeros and sign. */ const bool isZero() { - Bcd temp = stripLeadingZeros(this); - if (temp.numDigits > 1) return false; - return temp.lastDigit == 0; + Bcd b = canonical(this); + if (b.numDigits > 1) return false; + return b.lastDigit == 0; + } + + unittest { + write("isZero........."); + assert(ZERO.isZero); + Bcd b = Bcd(0); + assert(b.isZero); + b = "-00000001"; + assert(!b.isZero); + b = "-00000000"; + assert(b.isZero); + writeln("passed"); } //-------------------------------- @@ -383,25 +407,6 @@ */ void opAssign(const long n) { this = Bcd(n); -/+ uint m; - if (n < 0) { - sign = true; - m = std.math.abs(n); - } - else { - sign = false; - m = n; - } - Digit[20] dig; - int i = 0; - do { - uint q = m/10; - uint r = m%10; - dig[i] = cast(Digit) r; - m = q; - i++; - } while (m > 0); - digits = dig[0..i].dup;+/ } @@ -415,33 +420,6 @@ this.digits[index] = value; } -/+ int opApply(int delegate (ref Digit) dg) { - int result = 0; - for (int i = 0; i < numDigits; i++) { - result = dg(digits[i]); - if (result) break; - } - return result; - } - - int opApply(int delegate (ref Digit) dg) { - int result = 0; - for (int i = 0; i < numDigits; i++) { - result = dg(digits[i]); - if (result) break; - } - return result; - } - - int opApplyReverse(int delegate (ref Digit) dg) { - int result = 0; - for (int i = 0; i < numDigits; i++) { - result = dg(digits[i]); - if (result) break; - } - return result; - }+/ - //-------------------------------- // unary operators //-------------------------------- @@ -451,9 +429,7 @@ } const Bcd opNeg() { - Bcd copy = this.dup; - copy.sign = !this.sign; - return copy; + return negate(this); } const Bcd opCom() { @@ -651,15 +627,15 @@ } const bool opEquals(T:Bcd)(const T that) { - Bcd a = stripLeadingZeros(this); - Bcd b = stripLeadingZeros(that); + Bcd a = canonical(this); + Bcd b = canonical(that); + // TODO: this is wrong -- signs can differ -0 and + 0 if (this.sign != that.sign) return false; if (a.digits.length != b.digits.length) return false; foreach_reverse(int i, Digit digit; a.digits) { if (digit != b.digits[i]) return false; } return true; -// return compare(this, that) == 0; } const bool opEquals(T)(const T that) { @@ -684,6 +660,83 @@ } // end struct Bcd + +public bool isCanonical(const Bcd a) { + // no leading zeros + if (a.hasLeadingZeros) return false; + // not -0 + if (a.numDigits == 1 && a.firstDigit == 0) return !a.sign; + return true; +} + +// Strips leading zeros and changes sign if == -0 +public Bcd canonical(const Bcd a) { + Bcd d = a.dup; + if (isCanonical(a)) return d; + stripl(d); + if (d.numDigits == 1 && d.firstDigit == 0) { + d.sign = false; + } + return d; +} + +/** + * Strips leading zeros from the specified decint. + */ +private void stripl(ref Bcd a) { + if (a.hasLeadingZeros) { + foreach_reverse(int i, Digit digit; a.digits) { + if (i == 0 || digit != 0) { + a.digits.length = i+1; + break; + } + } + } +} + +unittest { + write("stripl......"); + Bcd b = Bcd("+00023"); + assert(b.toString == "00023"); + stripl(b); + assert(b.toString == "23"); + b = "-00050432"; + stripl(b); + assert(b.toString == "-50432"); + b = "-000"; + stripl(b); + assert(b.toString == "-0"); + writeln("passed"); +} + +/** + * Strips trailing zeros from the specified decint. + */ +private void stripr(ref Bcd a) { + int len = a.digits.length; + foreach(int i, Digit digit; a.digits) { + if (i == len-1 || digit != 0) { + a.digits = a.digits[i..$]; + break; + } + } +} + +unittest { + write("stripl......"); + Bcd b; + b = 23; + stripr(b); + assert(b == 23); + b = "-5000"; + stripr(b); + assert(b.toString == "-5"); + b = "-000"; + stripr(b); + assert(b.toString == "-0"); + writeln("passed"); +} + //public string format(const Bcd bcd, bool showPlus = false, bool showLeadingZeros = false) { public string format(const Bcd bcd) { int len = bcd.digits.length; @@ -750,7 +803,7 @@ bcd.digits[i] = cast(Digit)(ch - '0'); } - if (!lz) return stripLeadingZeros(bcd); + if (!lz) return canonical(bcd); return bcd; } @@ -759,7 +812,9 @@ } public Bcd negate(const Bcd a) { - return -a; + Bcd b = a.dup; + b.sign = !a.sign; + return b; } public Bcd abs(const Bcd a) { @@ -767,8 +822,8 @@ } public int compare(const Bcd m, const Bcd n) { - Bcd a = stripLeadingZeros(m); - Bcd b = stripLeadingZeros(n); + Bcd a = canonical(m); + Bcd b = canonical(n); if (!a.isSigned && b.isSigned) return 1; if (a.isSigned && !b.isSigned) return -1; if (a.digits.length > b.digits.length) return 1; @@ -780,35 +835,18 @@ return 0; } - // TODO: does this need a const and a dup? - // should it be public?? private would be okay -public Bcd stripLeadingZeros(const Bcd a) { - Bcd d = a.dup; - if (!a.hasLeadingZeros) return d; - int len = a.numDigits; - int i = 0; - while(i < len-1 && a.getDigit(i) == 0) { - i++; - } - d.digits.length = (len-i); // - 1); - if (d.numDigits == 1 && d.firstDigit == 0) { - d.sign = false; - } - return d; -} - unittest { write("strip..."); Bcd bcd; bcd = "+00123"; assert(bcd.toString == "00123"); - bcd = stripLeadingZeros(bcd); + bcd = canonical(bcd); // writeln("bcd = ", bcd); assert(bcd.toString == "123"); bcd = "+000"; // writeln("bcd = ", bcd); assert(bcd.toString == "000"); - bcd = stripLeadingZeros(bcd); + bcd = canonical(bcd); // writeln("bcd = ", bcd); writeln("passed"); } @@ -941,7 +979,7 @@ Bcd sum; if (a.sign == b.sign) { sum = simpleAdd(a, b); - sum = stripLeadingZeros(sum); + stripl(sum); sum.sign = a.sign; return sum; } @@ -964,11 +1002,11 @@ } else { sum = tensComp(sum); - sum = stripLeadingZeros(sum); } + stripl(sum); sum.sign = sign; } - return stripLeadingZeros(sum); + return sum; } unittest { @@ -1029,11 +1067,13 @@ sum[i] = 1; } else { - sum = stripLeadingZeros(sum); + stripl(sum); } return sum; } +// TODO: need a clipFirst, clipLast method + /** * Adds two digits and a carry digit. * Returns the (single-digit) sum and sets or resets the carry digit. @@ -1112,12 +1152,12 @@ Bcd n, m; Bcd p = Bcd(0, a.numDigits + b.numDigits); if (a.numDigits > b.numDigits) { - n = stripLeadingZeros(a); - m = stripLeadingZeros(b); + n = canonical(a); + m = canonical(b); } else { - n = stripLeadingZeros(b); - m = stripLeadingZeros(a); + n = canonical(b); + m = canonical(a); } foreach(uint i, Digit d; m.digits) { if (d == 0) continue; @@ -1186,96 +1226,13 @@ writeln("passed"); } -public Bcd truncate(const Bcd a, int n) { - Bcd dummy; - return truncate(a, n, dummy); -} - -unittest { - write("truncate...."); - Bcd a; - a = 1234567; - assert(truncate(a,3) == 123); - a = 10256; - assert(truncate(a,5) == 10256); - a = 8500; - assert(truncate(a,1) == 8); - a = -8500; - assert(truncate(a,1) == -8); - a = -8500; - assert(truncate(a,-1) == 0); - writeln("passed"); -} - -/** - * Truncate to the specified number of digits - */ -public Bcd truncate(const Bcd a, int n, out Bcd r) { - if (n <= 0) { - r = stripLeadingZeros(a); - return ZERO.dup; - } - if (n >= a.numDigits) { - r = ZERO.dup; - return stripLeadingZeros(a); - } - Bcd b; - b.digits = a.digits[$-n..$].dup; - b.sign = a.sign; - r.digits = a.digits[0..$-n].dup; - return b; -} - -unittest { - write("truncRem..."); - Bcd a; - Bcd r; - a = 1234567; - assert(truncate(a, 3, r) == 123); - assert(r == 4567); - a = 10256; - assert(truncate(a,5,r) == 10256); - assert(r == 0); - a = 8500; - assert(truncate(a,1,r) == 8); - assert(r == 500); - a = -8500; - assert(truncate(a,1,r) == -8); - assert(r == 500); - a = -8500; -// writeln("truncate(a, 6, r) = ", truncate(a, 6, r)); -// writeln("r = ", r); - assert(truncate(a,6,r) == -8500); - assert(r == 0); - writeln("passed"); -} - -public Bcd pad(Bcd a, int n) { - if (n <= a.numDigits) return a.dup; - return Bcd(a,n); -} - -unittest { - write("pad........."); - Bcd a; - a = 1234567; - assert(pad(a,3) == 1234567); - a = 10256; -// writeln("pad(a,9) = ", pad(a,9)); - assert(pad(a,9).toString == "000010256"); - a = 8500; - assert(pad(a,6).toString == "008500"); - a = -8500; - assert(pad(a,6).toString == "-008500"); - writeln("passed"); -} public Bcd divide(const Bcd a, const Bcd b, out Bcd r) { if (b == ZERO) throw new Exception("Divide by zero"); if (a == ZERO) return ZERO.dup; - Bcd d = stripLeadingZeros(abs(b)); - r = stripLeadingZeros(abs(a)); + Bcd d = canonical(abs(b)); + r = canonical(abs(a)); Bcd q; Bcd p = d; Bcd m; @@ -1351,8 +1308,92 @@ } +public Bcd truncate(const Bcd a, int n) { + Bcd dummy; + return truncate(a, n, dummy); +} + +unittest { + write("truncate...."); + Bcd a; + a = 1234567; + assert(truncate(a,3) == 123); + a = 10256; + assert(truncate(a,5) == 10256); + a = 8500; + assert(truncate(a,1) == 8); + a = -8500; + assert(truncate(a,1) == -8); + a = -8500; + assert(truncate(a,-1) == 0); + writeln("passed"); +} + +/** + * Truncate to the specified number of digits + */ +public Bcd truncate(const Bcd a, int n, out Bcd r) { + if (n <= 0) { + r = canonical(a); + return ZERO.dup; + } + if (n >= a.numDigits) { + r = ZERO.dup; + return canonical(a); + } + Bcd b; + b.digits = a.digits[$-n..$].dup; + b.sign = a.sign; + r.digits = a.digits[0..$-n].dup; + return b; +} + +unittest { + write("truncRem..."); + Bcd a; + Bcd r; + a = 1234567; + assert(truncate(a, 3, r) == 123); + assert(r == 4567); + a = 10256; + assert(truncate(a,5,r) == 10256); + assert(r == 0); + a = 8500; + assert(truncate(a,1,r) == 8); + assert(r == 500); + a = -8500; + assert(truncate(a,1,r) == -8); + assert(r == 500); + a = -8500; +// writeln("truncate(a, 6, r) = ", truncate(a, 6, r)); +// writeln("r = ", r); + assert(truncate(a,6,r) == -8500); + assert(r == 0); + writeln("passed"); +} + +public Bcd pad(Bcd a, int n) { + if (n <= a.numDigits) return a.dup; + return Bcd(a,n); +} + +unittest { + write("pad........."); + Bcd a; + a = 1234567; + assert(pad(a,3) == 1234567); + a = 10256; +// writeln("pad(a,9) = ", pad(a,9)); + assert(pad(a,9).toString == "000010256"); + a = 8500; + assert(pad(a,6).toString == "008500"); + a = -8500; + assert(pad(a,6).toString == "-008500"); + writeln("passed"); +} + public Bcd pow10(const Bcd a, uint n) { - Bcd b = stripLeadingZeros(a); + Bcd b = canonical(a); if (n == 0 || b == 0) return b; Digit[] zeros = new Digit[n]; b.digits = zeros ~ b.digits; @@ -1377,7 +1418,7 @@ } public Bcd shift(const Bcd a, int n) { - if (n == 0) return stripLeadingZeros(a); + if (n == 0) return canonical(a); if (n > 0) { // shift left return pow10(a, n); } @@ -1484,40 +1525,37 @@ * Rounds the specified integer to the specified number of digits. * The rounding mode governs the method of rounding. */ -public Bcd round(Bcd a, int n, Rounding mode) { +public Bcd round(const Bcd a, int n, Rounding mode) { Bcd r; Bcd b = truncate(a, n, r); - return b; + return round(b, r, mode); +// return b; } /** * Rounds the (truncated) integer based on its remainder and the rounding * mode. */ -public Bcd round(Bcd a, Bcd r, Rounding mode) { - Bcd b = a.dup; +public Bcd round(const Bcd a, const Bcd r, const Rounding mode) { + Bcd b = canonical(a); + ubyte rfd = r.firstDigit; switch (mode) { case Rounding.DOWN: return b; case Rounding.HALF_UP: - if (r.firstDigit >= 5) b++; + if (rfd >= 5) b++; return b; 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); - }+/ + if (rfd < 5) return b; + if (rfd > 5) return ++b; + Bcd p = r.dup; + stripr(p); + if (p.numDigits > 1) return ++b; + // to reach this point the remainder must be exactly 5 + // if odd, increment + if (b.lastDigit & 1 != 0) b++; return b; case Rounding.CEILING: @@ -1528,9 +1566,13 @@ if (b.isSigned && !r.isZero) b++; return b; - // TODO: is this one right? what about 55... case Rounding.HALF_DOWN: - if (r.firstDigit > 5) b++; + if (rfd < 5) return b; + if (rfd > 5) return ++b; + Bcd p = r.dup; + stripr(p); + if (p.numDigits > 1) return ++b; + // to reach this point the remainder must be exactly 5 return b; case Rounding.UP: @@ -1541,7 +1583,23 @@ } unittest { - write("rounding......."); + write("rounding...."); + Bcd a, b; + a = 12345; + b = round(a, 4, Rounding.DOWN); + assert(b == 1234); + b = round(a, 4, Rounding.HALF_UP); + assert(b == 1235); + b = round(a, 4, Rounding.CEILING); + assert(b == 1235); + b = round(a, 4, Rounding.HALF_EVEN); + assert(b == 1234); + b = round(a, 4, Rounding.FLOOR); + assert(b == 1234); + b = round(a, 4, Rounding.HALF_DOWN); + assert(b == 1234); + b = round(a, 4, Rounding.UP); + assert(b == 1235); writeln("passed"); }