Last active
February 4, 2017 13:18
-
-
Save decon3/ac4805378a8b511f45b130d7d6aea962 to your computer and use it in GitHub Desktop.
BCD converter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
// Note: Uses the Unpack() method found in | |
// https://github.com/thevinnie/UnpackDecimal/blob/master/SSISTask/UnpackDecimal/UnpackDecimal.cs | |
namespace Utils.Conversions | |
{ | |
/// <summary> | |
/// Handles Binary Coded Decimal conversions | |
/// </summary> | |
/// <remarks> | |
/// Eg. for handling amount in dollars & cents: | |
/// var converter = new BcdConverter(2); | |
/// var bcd = converter.PackAmount(10.23M); | |
/// var amount = converter.UnpackAmount(bcd); | |
/// </remarks> | |
public class BcdConverter | |
{ | |
int _precision; | |
public BcdConverter(int precision) | |
{ | |
_precision = precision; | |
} | |
public string PackAmount(decimal amount) | |
{ | |
return PackAmount(amount, _precision); | |
} | |
public string PackAmount(decimal amount, int precision) | |
{ | |
Stack<byte> bcd = new Stack<byte>(10); | |
long value = StripDecimalPoint(amount); | |
byte currentByte; | |
if (value < 0) | |
{ | |
currentByte = 0x0d; | |
value = -value; | |
} | |
else | |
{ | |
currentByte = 0x0c; | |
} | |
bool byteComplete = false; | |
while (value != 0) | |
{ | |
if (byteComplete) | |
currentByte = (byte)(value % 10); | |
else | |
currentByte |= (byte)((value % 10) << 4); | |
value /= 10; | |
byteComplete = !byteComplete; | |
if (byteComplete) | |
bcd.Push(currentByte); | |
} | |
if (!byteComplete) | |
bcd.Push(currentByte); | |
int nibbles = precision + 2 + 1; //precision + 2 decimals + 1 sign | |
var bcdLength = nibbles % 2 == 0 ? nibbles / 2 : (nibbles + 1) / 2; // even number of bytes | |
// ensure that the resulting bcd string be of fixed length, independent of the value | |
// i.e., 10.00 or 10000.00 should be converted into BCD strings of same length | |
// (length is determined by precision) | |
if (bcd.Count < bcdLength) | |
{ | |
var padding = bcdLength - bcd.Count; | |
for (var i = 0; i < padding; i++) | |
{ | |
bcd.Push((byte)0); | |
} | |
} | |
if (bcd.Count > bcdLength) | |
throw new ArgumentOutOfRangeException("amount", amount + " - precision is greater than allowed, namely, " + precision); | |
return new string(bcd.ToArray().Select(x => (char)x).ToArray()); | |
} | |
public Decimal UnpackAmount(string bcd) | |
{ | |
byte[] inp = bcd.Select(c => (byte)c).ToArray(); | |
long lo = 0; | |
long mid = 0; | |
long hi = 0; | |
bool isNegative; | |
// this nybble stores only the sign, not a digit. | |
// "C" hex is positive, "D" hex is negative, and "F" hex is unsigned. | |
switch (nibble(inp, 0)) | |
{ | |
case 0x0D: | |
isNegative = true; | |
break; | |
case 0x0F: | |
case 0x0C: | |
isNegative = false; | |
break; | |
default: | |
throw new Exception("Bad sign nibble"); | |
} | |
long intermediate; | |
long carry; | |
long digit; | |
for (int j = inp.Length * 2 - 1; j > 0; j--) | |
{ | |
// multiply by 10 | |
intermediate = lo * 10; | |
lo = intermediate & 0xffffffff; | |
carry = intermediate >> 32; | |
intermediate = mid * 10 + carry; | |
mid = intermediate & 0xffffffff; | |
carry = intermediate >> 32; | |
intermediate = hi * 10 + carry; | |
hi = intermediate & 0xffffffff; | |
carry = intermediate >> 32; | |
// By limiting input length to 14, we ensure overflow will never occur | |
digit = nibble(inp, j); | |
if (digit > 9) | |
{ | |
throw new Exception("Bad digit"); | |
} | |
intermediate = lo + digit; | |
lo = intermediate & 0xffffffff; | |
carry = intermediate >> 32; | |
if (carry > 0) | |
{ | |
intermediate = mid + carry; | |
mid = intermediate & 0xffffffff; | |
carry = intermediate >> 32; | |
if (carry > 0) | |
{ | |
intermediate = hi + carry; | |
hi = intermediate & 0xffffffff; | |
carry = intermediate >> 32; | |
// carry should never be non-zero. Back up with validation | |
} | |
} | |
} | |
return new Decimal((int)lo, (int)mid, (int)hi, isNegative, 2); | |
} | |
private long StripDecimalPoint(decimal amount) | |
{ | |
return Convert.ToInt64(amount * 100); | |
} | |
private int nibble(byte[] inp, int nibbleNo) | |
{ | |
int b = inp[inp.Length - 1 - nibbleNo / 2]; | |
return (nibbleNo % 2 == 0) ? (b & 0x0000000F) : (b >> 4); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment