UPDATE: If you downloaded a version from before July 1, 2010, please download the new version 2.0. It was improved and made faster. The files uploaded May 21 were apparently still not good enough, see below
To calculate non-integral numbers, most programming languages provide floating point types. These are usually fast, because they often are FPU-supported, but there are quite a few problems with their accuracy. More about the problems you can expect can be found in my article about Floating Point Numbers.
In Microsoft .NET, there is a type, called Decimal that is not FPU-supported, but very accurate in a defined range. The Decimals unit tries to emulate this type as well as possible, in Delphi (and a lot of assembler). The internal format is exactly the same, but the routines that manipulate the bits were entirely written by me.
Currently, this is a Win32 type only. Most important routines are in 32 bit assembler. I intend to write a 64 bit assembler version of these routines too, and even a “PUREPASCAL” (i.e. no assembler) version.
The Decimal type
Unlike the FPU-supported types, this type uses a decimal “exponent”, although in this case the exponent is more like a scaling factor, and has a range of 0..28. The mantissa is binary, but much larger than that of the FPU types: it contains 96 bits. The sign bit is stored separately. The exact format is like this:
|000..095||Mantissa||Unsigned 96 bit integer|
|096..111||Reserved||Must be 0|
|112..116||Scale||Range 0..28, where 0 stands for × 100 and 28 stands for × 10−28|
|117..126||Reserved||Must be 0|
|127||Sign bit||1 = negative, 0 = positive|
Unlike most other floating point types, this type has a different format for values like 0.1 and 0.1000. The first value is stored as mantissa 1 and a scaling factor of 1 (i.e. as 1 × 10−1). The second is stored as 1000 with a scaling factor of 4 (i.e. as 1000 × 10−4), etc. In other words, this type “remembers”, as much as possible, the number of decimals entered. Of course, if you multiply or divide, you will get more decimals.
Although values like 0.123 and 0.1230000 are stored differently, the comparison routines and operators recognize them as equal.
The Decimal type is a record type. That enables it to be used like any other numeric type. It has operators for comparison, simple arithmetic like addition, subtraction, multiplication and division, and conversion operators to and from other numeric types.
A simple code example:
var A, B, C: Decimal; begin A := '1.2345'; // I said it was a little simple B := '3.49'; C := A + B; Writeln('C = ', C.ToString); end;
The output is, as expected:
C = 4.7245
Initialization is best done using one of the conversion operators. If you want a specific format, with a specific number of decimals, use the conversion operator that converts from a string:
myDecimal := '1.3456';
If you want to control the exact contents of the Decimal, you can use the following constructor:
constructor Create(Lo, Mid, Hi: Longword; IsNegative: Boolean; Scale: Byte);
To get the exact contents, you can use the GetBits function.
Additionally to the functions and operators discussed above, Decimal has a full set of conversion functions and the usual mathematic functions, like Abs, Floor, etc. There is a ToString function that converts the Decimal to a string. The other ToString function that takes a Format string is not functional yet, but is being worked on.
The simple DecimalDevelopment program shows a few ways to use the type.
As I already mentioned, the type is not FPU-supported. This means it is a lot slower than the normal FPU types like Single, Double or Extended. I did my best to make the routines as fast as I could, using assembler everywhere it made sense, but 96 bit multiplications or divisions are not really fast, even when they are done in assembler. This produces the desired results, but I’m glad about any suggestion to speed up the type. Just e-mail me.
I managed to make the basic math operations (addition, subtraction, multiplication and division) quite a bit faster. Division is about 8 times as fast as it used to be, addition and subtraction are about twice as fast and multiplication is also a little faster (there was not a lot I could do to optimize multiplication anymore). Division uses a totally different algorithm (basecase, see source) and all of the four operations profit from better scaling routines.
The version from May 21 appeared to be still buggy. So I took some inspiration from some tests I had seen from the Mono project, and did something similar: I created a simple C# program that uses an array with a range of decimal values to calculate the basic operations on each against each other. The result values are written as a Pascal include file that can immediately be included in a Delphi program that runs the same tests using my Decimal code, and checks the results against the generated result arrays. The code of the result generator is included in the decimals.zip file, and so are the test program and the generated include file. The C# program can be compiled with the freely available Microsoft Visual C# Express or a regular Visual C#.
I changed the code of the Decimals unit thus that now, no error should be generated anymore. In other words, the results produced should be exactly the same as those produced by .NET.
If you want to enhance the test program with your own values, be sure to simply add the values to the C# program and generate a new include file. That can then be used – as is – by the Delphi test program. If you still find any discrepancies, I’d love to hear about them.
I hope this code is useful to you. If you use some of it, please credit me. If you modify or improve the unit, please send me the modifications.
I may improve or enhance the unit myself, and I will try to post changes here. But this is not a promise. Please don’t request features.
Standard Disclaimer for External Links
These links are being provided as a convenience and for informational purposes only; they do not constitute an endorsement or an approval of any of the products, services or opinions of the corporation or organization or individual. I bear no responsibility for the accuracy, legality or content of the external site or for that of subsequent links. Contact the external site for answers to questions regarding its content.
Disclaimer and Copyright
The coding examples presented here are for illustration purposes only. The author takes no responsibility for end-user use. All content herein is copyrighted by Rudy Velthuis, and may not be reproduced in any form without the author's permission. Source code written by Rudy Velthuis presented as download is subject to the license in the files.