view src/decimal/bcd.d @ 4:b37c218c1442

Initial development of BCD integers
author Paul (paul.d.anderson@comcast.net)
date Thu, 18 Mar 2010 18:10:25 -0700
parents
children c021ed211f89
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.
**/

module decimal.bcd;

import std.conv: ConvError;
import std.math;
import std.stdio: write, writeln;
import std.string: strip;

alias ubyte Digit;

public static immutable Bcd ZERO = { digits:[0] };
public static immutable Bcd ONE  = { digits:[1] };
public static immutable Bcd NEG_ONE = { sign:true, digits:[1] };

public struct Bcd {

	// members
	private bool sign = false;
	private Digit[] digits = [ 0 ];
	
	// constructors
	public this(const Bcd bcd) {
		this = bcd;
	}
	
	public this(const Bcd bcd, uint numDigits) {
		this = bcd;
		setNumDigits(numDigits);
	}
	
	public this(const string str) {
		this = str;
	}
	
	public this(const long n) {
		this = n;
	}
		
	public this(const long n, uint numDigits) {
		this = n;
		setNumDigits(numDigits);
	}
		
	const string toString() {
		return format(this);
	}
	
	const hash_t toHash() {
		hash_t hash = 0;
		foreach(Digit digit; digits) {
			hash += digit;
		}
		return hash;
	}
	
	// access methods	
	const uint numDigits() {
		return digits.length;
	}
	
	void setNumDigits(uint n) {
		digits.length = n;
	}
	
	const uint firstDigit() {
		return digits[$-1];
	}
	
	const uint lastDigit() {
		return digits[0];
	}
	
	void setDigit(uint n, Digit value) {
		digits[$-n-1] = value;
	}
	
	const int getDigit(int n) {
		return digits[$-n-1];
	}
	
	unittest {
		write("digits...");
		Bcd bcd = Bcd(12345678L);
		assert(bcd.numDigits() == 8);
		assert(bcd.firstDigit() == 1);
		assert(bcd.lastDigit() == 8);
		assert(bcd.getDigit(3) == 4);
		bcd.setDigit(3, 7);
		assert(bcd.getDigit(3) == 7);
		bcd.setNumDigits(10);
		assert(bcd.numDigits == 10);
		assert(bcd.firstDigit() == 0);
		assert(bcd.getDigit(5) == 7);
		bcd.setNumDigits(5);
		assert(bcd.getDigit(2) == 6);
//		writeln("bcd = ", bcd);
		writeln("passed!");
	}
	
	const bool isSigned() {
		return sign;
	}
	
	const bool isZero() {
		writeln("stripping");
		Bcd temp = stripLeadingZeros(this);
		writeln("stripped");
		writeln("temp = ", temp);
		if (temp.numDigits > 1) return false;
		return temp.lastDigit == 0;
	}
	
	const bool hasLeadingZeros() {
		return numDigits > 0 && firstDigit == 0;
	}
	
	void opAssign(const Bcd that) {
		this.sign = that.sign;
		this.digits.length = that.digits.length;
		this.digits[] = that.digits[];
	}
	
	void opAssign(const string str) {
		this = parse(str);
	}
	
	void opAssign(const long 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;
	}
	
	
	// index operator
	Digit opIndex(uint index) {
		return this.digits[index];
	}
	
	// index assign operator
	void opIndexAssign(Digit value, uint index) {
		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;
	}+/
	
	const Bcd dup() {
		Bcd bcd;
		bcd.sign = this.sign;
		bcd.digits.length = this.digits.length;
		bcd.digits[] = this.digits[];
		return bcd;
	}
	
	// TODO: always uncertain how this is done...will it make an unnecessary copy?
	const Bcd opPos() {
		return this.dup;
	}
	
	const Bcd opNeg() {
		return negate(this);
	}
	
	const Bcd opCom() {
		return complement(this);
	}
	
	const Bcd opNot() {
		return not(this);
	}
	
	const Bcd opAnd(const Bcd bcd) {
		return and(this, bcd);
	}
	
	const Bcd opOr(const Bcd bcd) {
		return or(this, bcd);
	}
	
	const Bcd opXor(const Bcd bcd) {
		return xor(this, bcd);
	}
	
	// TODO: what's the matter with the one-digit numbers??
	const bool opEquals(T:Bcd)(const T that) {
//		writeln("equating");
		if (this.sign != that.sign) return false; 	// what about +/- zero?
//		writeln("same signs");
		Bcd thisOne = stripLeadingZeros(this);
//		writeln("this = ", thisOne);
		Bcd thatOne = stripLeadingZeros(that);
//		writeln("that = ", thatOne);
		if (thisOne.digits.length != thatOne.digits.length) return false;
//		writeln("length = ", thisOne.digits.length);
//		foreach(int i, Digit digit; thisOne) {	// todo: have to make opApply here.
//		}
		foreach_reverse(int i, Digit digit; thisOne.digits) {
/+			writeln("i = ", i);
			writeln("this[i] = ", thisOne.digits[i]);
			writeln("that[i] = ", thatOne.digits[i]);
			writeln("this = ", thisOne.digits);
			writeln("that = ", thatOne.digits);+/
			if (digit != thatOne.digits[i]) return false;
		}
		return true;
	}
	
	const bool opEquals(T)(const T that) {
		return opEquals(Bcd(that));
	}
	
} // end struct Bcd

private bool isDigit(const char ch) {
	return (ch >= '0' && ch <= '9');
}

public string format(const Bcd bcd, bool showPlus = false, bool showLeadingZeros = false) {
	int len = bcd.digits.length;
	if (bcd.sign) len++;
	char[] str = new char[len];
	
	int index = 0;
	if (bcd.sign) {
		str[index] = '-';
		index++;
	}
	
	foreach_reverse(Digit digit; bcd.digits) {
		str[index] = cast(char) digit + '0';
		index++;
	}
	return str.idup;
}

public Bcd parse(string str) {
	Bcd bcd;
	// are leading zeros retained?
	bool lz = false;

	// strip whitespace, convert to char array
	char[] str1 = str.strip.dup;
	
	// check for leading '-'
	if (str1[0] == '-') {
		bcd.sign = true;
		str1 = str1[1..$];
		lz = true;
	}
	
	// check for leading '+'
	if (str1[0] == '+') {
		bcd.sign = false;
		str1 = str1[1..$];
		lz = true;
	}
	
	// verify chars are digits, strip out underscores
	int index = 0;
	char[] str2 = new char[str1.length];
	foreach_reverse(char ch; str1) {
		if (isDigit(ch)) {
			str2[index] = ch;
			index++;
			continue;
	    }
		if (ch == '_') {
			continue;
		}
		throw (new ConvError("Invalid character: " ~ ch));
	}
	str2.length = index;
	
	bcd.digits.length = index;
	foreach(int i, char ch; str2) {
		bcd.digits[i] = cast(Digit)(ch - '0');
	}

	if (!lz)  return stripLeadingZeros(bcd);	
	return bcd;
}

public Bcd copy(const Bcd bcd) {
	return bcd.dup;
}

public Bcd negate(const Bcd bcd) {
	Bcd copy = bcd; //.dup;
	copy.sign = !bcd.sign;
	return copy;
}

public Bcd abs(const Bcd bcd) {
	return bcd.isSigned ? -bcd: +bcd;
}

public Bcd stripLeadingZeros(const Bcd bcd) {
	Bcd result = bcd.dup;
	if (!bcd.hasLeadingZeros) return result;
	int len = bcd.numDigits;
	int i = 0;
	while(i < len-1 && bcd.getDigit(i) == 0) {
		i++;
	} 
	result.setNumDigits(len - i);
	return result;
}


unittest {
	write("strip...");
	Bcd bcd;
	bcd = "+00123";
	assert(bcd.toString == "00123");
	bcd = stripLeadingZeros(bcd);
	writeln("bcd = ", bcd);
	assert(bcd.toString == "123");
	bcd = "+000";
	writeln("bcd = ", bcd);
	assert(bcd.toString == "000");
	bcd = stripLeadingZeros(bcd);
	writeln("bcd = ", bcd);
	writeln("passed");
}
	
public bool sameLength(const Bcd a, const Bcd b) {
	return a.numDigits == b.numDigits;
}

public int setSameLength(ref Bcd a, ref Bcd b) {
	if (sameLength(a,b)) return a.numDigits;
	uint alen = a.numDigits;
	uint blen = b.numDigits;
	if (alen > blen) {
		b.setNumDigits(alen);
	}
	else {
		a.setNumDigits(blen);
	}
	return a.numDigits();
}

public Bcd complement(const Bcd a) {
	Bcd b = Bcd(0, a.numDigits);
	foreach(int i, Digit digit; a.digits) {
		b.digits[i] = digit == 0 ? 1 : 0 ;
	}
	return b;
}

unittest {
	write("com...");
	Bcd bcd;
	bcd = 123;
	assert(complement(bcd).toString == "000");
	bcd = 40509;
	assert(complement(bcd).toString == "01010");
//	writeln("bcd = ", bcd);
//	writeln("~bcd = ", complement(bcd));
	writeln("passed");
}
	
public int sgn(Bcd bcd) {
	if (bcd.isZero) return 0;
	return bcd.isSigned ? -1 : 1;
}

public Bcd not(const Bcd x) {
	Bcd result = x.dup;
	for (int i = 0; i < x.numDigits; i++) {
		result[i] = not(result[i]);
	}
	return result;
}

private Digit not(const Digit a) {
	return a != 0;
}

public Bcd and(const Bcd x, const Bcd y) {
	Bcd a = x.dup;
	Bcd b = y.dup;
	int len = setSameLength(a,b);
	Bcd result = Bcd(0, len);
	for (int i = 0; i < len; i++) {
		result[i] = and(a[i], b[i]);
	}
	return result;
}

private Digit and(const Digit a, const Digit b) {
	return a != 0 && b != 0;
}

unittest {
	write("and...");
	Bcd a;
	Bcd b;
	a = 123000;
	b = 123000;
	assert(and(a,b).toString == "111000");
	b = 12300;
	assert(and(a,b).toString == "011000");
	a = 1234567;
	b = 7654321;
	assert(and(a,b).toString == "1111111");
	a = 1010;
	b = 101;
	writeln("a = ", a);
	writeln("b = ", b);
	writeln("and = ", and(a,b));
	assert(and(a,b).isZero);
	writeln("passed!");
}

// TODO: looks like there's some room for templates or mixins here.
public Bcd or(const Bcd x, const Bcd y) {
	Bcd a = x.dup;
	Bcd b = y.dup;
	int len = setSameLength(a,b);
	Bcd result = Bcd(0, len);
	for (int i = 0; i < len; i++) {
		result[i] = or(a[i], b[i]);
	}
	return result;
}

private Digit or(const Digit a, const Digit b) {
	return a != 0 || b != 0;
}


public Bcd xor(const Bcd x, const Bcd y) {
	Bcd a = x.dup;
	Bcd b = y.dup;
	int len = setSameLength(a,b);
	Bcd result = Bcd(0, len);
	for (int i = 0; i < len; i++) {
		result[i] = xor(a[i], b[i]);
	}
	return result;
}

private Digit xor(const Digit a, const Digit b) {
	return (a == 0 && b != 0) || (a != 0 && b == 0);
}

public Bcd add(const Bcd a, const Bcd b) {
	Bcd x = a.dup;
	Bcd y = b.dup;
	int len = setSameLength(x, y);
	Bcd result = Bcd(0, len+1);
	Digit carry = 0;
	uint i;
	for (i = 0; i < len; i++) {
		result[i] = add(x[i], y[i], carry);
	}
	if (carry) {
		result[i] = 1;
	}
	else {
		result = stripLeadingZeros(result);
	}
	return result;
}

private Digit add(const Digit a, const Digit b, ref Digit carry) {
	Digit sum = a + b + carry;
	if (sum > 9) {
		sum -= 10;
		carry = 1;
	}
	else {
		carry = 0;
	}
	return sum;
}

unittest {
	write("add...");
	Bcd a, b;
	a = 123;
	b = 222;
	Bcd sum = add(a,b);
	assert(sum == 345);
	a = 2;
	b = 102000;
	sum = add(a,b);
	assert(sum == 102002);
	writeln("passed!");
}

//==========================================

public void main() {
	writeln("Hello, world");
	Bcd bcd;
	writeln("bcd = ", format(bcd));
	bcd = parse("12");
	writeln("bcd = ", format(bcd));
	bcd = parse("012");
	writeln("bcd = ", format(bcd));
	bcd = parse("-012");
	writeln("bcd = ", format(bcd));
	bcd = parse("+012");
	writeln("bcd = ", format(bcd));
	bcd = parse("-0");
	writeln("bcd = ", format(bcd));
	bcd = parse("012_345_678");
	writeln("bcd = ", format(bcd));

	bcd = 1234;
	writeln("bcd = ", format(bcd));
	bcd = 12345678L;
	writeln("bcd = ", format(bcd));
	writeln("a == b : ", Bcd(0) == Bcd(0));
	writeln("a == b : ", Bcd(4) == Bcd(4));
	writeln("a == b : ", Bcd(40) == Bcd(40));
	writeln("a == b : ", Bcd(-400) == Bcd(-400));
	writeln("a == b : ", Bcd(12345678) == Bcd(+12345678));
	writeln("a != b : ", Bcd(40) == Bcd(53));
	writeln("a != b : ", Bcd(402) == Bcd(531));
	writeln("a != b : ", Bcd(402) == Bcd(-402));
    writeln("a != b : ", Bcd(65432) == Bcd(65431));
	writeln("a != b : ", Bcd(2) == Bcd(1));
	writeln("a != b : ", Bcd(1) == Bcd(2));
	writeln("OK!");
}