changeset 9:48d564218e05

Added rounding, additional unit tests, strip leading and trailing zeros.
author Paul (paul.d.anderson@comcast.net)
date Wed, 24 Mar 2010 22:21:38 -0700
parents c991b9fde45c
children f925fe996255
files src/decimal/bcd.d
diffstat 1 files changed, 255 insertions(+), 197 deletions(-) [+]
line wrap: on
line diff
--- 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");
 }