|
Binary Representation of 'decimal' Values
The binary represenation of a "decimal" value is well described by the description of the
Decimal.GetBits() method in the Microsoft .NET Framework Reference manual.
The binary representation of a "decimal" value requires 128 bits with the following layout:
- 1 bit (bit 127) is used for the sign (s) as a integer.
- 7 bits (bits 126 to 120) are not used.
- 8 bits (bits 119 to 112) are used for the exponent (e) as a integer.
- 16 bits (bits 111 to 96) are not used.
- 96 bits (bits 95 to 0) are used for the coeficient (c) as a integer.
The value is then represented through the following expression:
v = (-1)^s * c * 10^(-e)
The following sample program illustrates the binary representation of several
"decimal" values:
using System;
public class DecimalBits {
public static void Main() {
Console.WriteLine("s c e v");
WriteBits(1.0m);
WriteBits(256.0m*256*256*128);
WriteBits(256.0m*256*256*256-1);
WriteBits(256.0m*256*256*256);
WriteBits(0.1m);
WriteBits(0.01m);
WriteBits(100.0m);
WriteBits(-1.0m);
WriteBits(1.0m/3);
WriteBits(2.0m/3);
}
public static void WriteBits(decimal value) {
int[] bits;
bits = decimal.GetBits(value);
Console.Write("{0}",((bits[3]>>31)&0x1).ToString("X1"));
Console.Write(" __{0}____",
((bits[3]&0x00ff0000)>>16).ToString("X2"));
Console.Write(" ");
for (int i=1; i<4; i++) {
Console.Write("{0} ",bits[3-i].ToString("X8"));
}
Console.WriteLine("{0}",value);
}
}
Output:
s c e v
0 __00____ 00000000 00000000 00000001 1
0 __00____ 00000000 00000000 80000000 2147483648
0 __00____ 00000000 00000000 FFFFFFFF 4294967295
0 __00____ 00000000 00000001 00000000 4294967296
0 __01____ 00000000 00000000 00000001 0.1
0 __02____ 00000000 00000000 00000001 0.01
0 __00____ 00000000 00000000 00000064 100
1 __00____ 00000000 00000000 00000001 -1
0 __1C____ 0AC544CA 14B700CB 05555555 0.3333333333333333333333333333
0 __1C____ 158A8994 296E0196 0AAAAAAB 0.6666666666666666666666666667
Comparing to the binary representation of "double", "decimal" has the advantage
of capturing 4 times more binary digits, therefor, more accurate. But to keep
4 times more digits, there is big extra cost in carring out the operations.
As one of my earlier sample codes shows, "decimal" multiply and divide are about
27 times slower than "decimal".
How much more accurate is "decimal" than "double"? Hope the following program
will help us answer this question:
using System;
class Accuracy {
static void Main() {
long m = 1000;
long n = 85;
long i;
double ds, df, da, dmin, dave, dmax;
decimal ms, mf, ma, mmin, mave, mmax;
dave = 0.0d;
mave = 0.0m;
dmin = double.MaxValue;
mmin = decimal.MaxValue;
dmax = 0.0d;
mmax = 0.0m;
Random r = new Random();
for (i=0; i<m; i++) {
ds = 0.5d + r.NextDouble()/2;
df = 0.5d + r.NextDouble()/2;
ms = (decimal)ds;
mf = (decimal)df;
da = LoopDouble(n, ref ds, ref df);
ma = LoopDecimal(n, ref ms, ref mf);
dave += da;
mave += ma;
if (da<dmin) dmin = da;
if (ma<mmin) mmin = ma;
if (da>dmax) dmax = da;
if (ma>mmax) mmax = ma;
}
dave = dave/m;
mave = mave/m;
Console.WriteLine("# of tests: {0}", m);
Console.WriteLine("# of operations: {0}", n);
Console.WriteLine("Accuracy with double:");
Console.WriteLine(" Average: {0}", dave);
Console.WriteLine(" Minimum: {0}", dmin);
Console.WriteLine(" Maximum: {0}", dmax);
Console.WriteLine("Accuracy with decimal:");
Console.WriteLine(" Average: {0}", mave);
Console.WriteLine(" Minimum: {0}", mmin);
Console.WriteLine(" Maximum: {0}", mmax);
}
private static double LoopDouble(long n, ref double s, ref double f) {
long j;
double o = s;
for (j=1; j<=n; j++) {
s = s*f;
}
for (j=1; j<=n; j++) {
s = s/f;
}
return Math.Abs((s-o)/s);
}
private static decimal LoopDecimal(long n, ref decimal s,
ref decimal f) {
long j;
decimal o = s;
for (j=1; j<=n; j++) {
s = s*f;
}
for (j=1; j<=n; j++) {
s = s/f;
}
return Math.Abs((s-o)/s);
}
}
Output:
# of tests: 1000
# of operations: 85
Accuracy with double:
Average: 4.43205488617925E-16
Minimum: 0
Maximum: 1.94830995098999E-15
Accuracy with decimal:
Average: 1.3063460507474029114787E-05
Minimum: 0
Maximum: 0.0021586944781599286958505203
Well, the result is very supprising and dispointing. A value between
0.5 and 1.0 multiply by another value in the same range for 85 times,
then divided by the second value 85 times. The resulting value should the same
as the first value, if all the operations are 100% accurate. However, this
program shows that the "double" values and operations did pretty well with
relative errors in the order of 1.0E-16, comparing to 1.0E-5 for "decimal"
values and operations.
So, what's going on here? What's the benefits of "decimal"?
|