The decNumber Library, version 3.68
Copyright (c) IBM Corporation, 2010. All rights reserved. © | 23 Jan 2010 |
[previous | contents | next] |
Conversions to and from the decNumber format are fast; they are usually faster than all but the simplest calculations (x=x+1, for example). Therefore, in general, the cost of conversions is small compared to that of calculation.
The coded formats currently provided for in the library are
However, when arbitrary-precision calculation is not required (that is, up to 34 digits of precision is all that is required) it is even more efficient to use one of the decFloats modules for arithmetic and other operations. The decFloats modules work directly from the decimal-encoded compressed formats and avoid the need for conversions to and from the decNumber format. Tables comparing the performance of the decFloats modules with decNumber can be found in Appendix A.
The remainder of this section illustrates the use of the coded formats and the decFloats modules in conjunction with the core decContext and decNumber modules by means of examples.
/* stdint.h -- some standard integer types from C99 */ typedef unsigned char uint8_t; typedef char int8_t; typedef unsigned short uint16_t; typedef short int16_t; typedef unsigned int uint32_t; typedef int int32_t; typedef unsigned long long uint64_t; typedef long long int64_t;You may need to change these if (for example) the int type in your compiler does not describe a 32-bit integer. If there are no 64-bit integers available with your compiler, set the DECUSE64 tuning parameter to 0; the last two typedefs above are then not needed.
1. // example1.c -- convert the first two argument words to decNumber, 2. // add them together, and display the result 3. 4. #define DECNUMDIGITS 34 // work with up to 34 digits 5. #include "decNumber.h" // base number library 6. #include <stdio.h> // for printf 7. 8. int main(int argc, char *argv[]) { 9. decNumber a, b; // working numbers 10. decContext set; // working context 11. char string[DECNUMDIGITS+14]; // conversion buffer 12. 13. if (argc<3) { // not enough words 14. printf("Please supply two numbers to add.\n"); 15. return 1; 16. } 17. decContextDefault(&set, DEC_INIT_BASE); // initialize 18. set.traps=0; // no traps, thank you 19. set.digits=DECNUMDIGITS; // set precision 20. 21. decNumberFromString(&a, argv[1], &set); 22. decNumberFromString(&b, argv[2], &set); 23. decNumberAdd(&a, &a, &b, &set); // a=a+b 24. decNumberToString(&a, string); 25. printf("%s + %s => %s\n", argv[1], argv[2], string); 26. return 0; 27. } // mainThis example is a complete, runnable program. In later examples we’ll leave out some of the ‘boilerplate’, checking, etc., but this one should compile and be usable as it stands.
Lines 1 and 2 document the purpose of the program.
Line 4 sets the maximum precision of decNumbers to be used by the program, which is used by the embedded header file in line 5 (and also elsewhere in this program).
Line 6 includes the C library for input and output, so we can use the printf function. Lines 8 through 11 start the main function, and declare the variables we will use. Lines 13 through 16 check that enough argument words have been given to the program.
Lines 17–19 initialize the decContext structure, turn off error signals, and set the working precision to the maximum possible for the size of decNumbers we have declared.
Lines 21 and 22 convert the first two argument words into numbers; these are then added together in line 23, converted back to a string in line 24, and displayed in line 25.
Note that there is no error checking of the arguments in this example, so the result will be NaN (Not a Number) if one or both words is not a number. Error checking is introduced in Example 3.
100000 at 6.5% for 20 years => 352364.51The heart of the program is:
1. decNumber one, mtwo, hundred; // constants 2. decNumber start, rate, years; // parameters 3. decNumber total; // result 4. decContext set; // working context 5. char string[DECNUMDIGITS+14]; // conversion buffer 6. 7. decContextDefault(&set, DEC_INIT_BASE); // initialize 8. set.traps=0; // no traps 9. set.digits=25; // precision 25 10. decNumberFromString(&one, "1", &set); // set constants 11. decNumberFromString(&mtwo, "-2", &set); 12. decNumberFromString(&hundred, "100", &set); 13. 14. decNumberFromString(&start, argv[1], &set); // parameter words 15. decNumberFromString(&rate, argv[2], &set); 16. decNumberFromString(&years, argv[3], &set); 17. 18. decNumberDivide(&rate, &rate, &hundred, &set); // rate=rate/100 19. decNumberAdd(&rate, &rate, &one, &set); // rate=rate+1 20. decNumberPower(&rate, &rate, &years, &set); // rate=rate**years 21. decNumberMultiply(&total, &rate, &start, &set); // total=rate*start 22. decNumberRescale(&total, &total, &mtwo, &set); // two digits please 23. 24. decNumberToString(&total, string); 25. printf("%s at %s%% for %s years => %s\n", 26. argv[1], argv[2], argv[3], string); 27. return 0;These lines would replace the content of the main function in Example 1 (adding the check for the number of parameters would be advisable).
As in Example 1, the variables to be used are first declared and initialized (lines 1 through 12), with the working precision being set to 25 in this case. The parameter words are converted into decNumbers in lines 14–16.
The next four function calls calculate the result; first the rate is changed from a percentage (e.g., 6.5) to a per annum rate (1.065). This is then raised to the power of the number of years (which must be a whole number), giving the rate over the total period. This rate is then multiplied by the initial investment to give the result.
Next (line 22) the result is rescaled so it will have only two digits after the decimal point (an exponent of -2), and finally (lines 24–26) it is converted to a string and displayed.
One way to check for errors would be to check the status field of the decContext structure after every decNumber function call. However, as that field accumulates errors until cleared deliberately it is often more convenient and more efficient to delay the check until after a sequence is complete.
This passive checking is easily added to Example 2. Replace lines 14 through 22 in that example with (the original lines repeated here are unchanged):
1. decNumberFromString(&start, argv[1], &set); // parameter words 2. decNumberFromString(&rate, argv[2], &set); 3. decNumberFromString(&years, argv[3], &set); 4. if (set.status) { 5. printf("An input argument word was invalid [%s]\n", 6. decContextStatusToString(&set)); 7. return 1; 8. } 9. decNumberDivide(&rate, &rate, &hundred, &set); // rate=rate/100 10. decNumberAdd(&rate, &rate, &one, &set); // rate=rate+1 11. decNumberPower(&rate, &rate, &years, &set); // rate=rate**years 12. decNumberMultiply(&total, &rate, &start, &set); // total=rate*start 13. decNumberRescale(&total, &total, &mtwo, &set); // two digits please 14. if (set.status & DEC_Errors) { 15. set.status &= DEC_Errors; // keep only errors 16. printf("Result could not be calculated [%s]\n", 17. decContextStatusToString(&set)); 18. return 1; 19. }Here, in the if statement starting on line 4, the error message is displayed if the status field of the set structure is non-zero. The call to decContextStatusToString in line 6 returns a string which describes a set status bit (probably ‘Conversion syntax’).
In line 14, the test is augmented by anding the set.status value with DEC_Errors. This ensures that only serious conditions trigger the message. In this case, it is possible that the DEC_Inexact and DEC_Rounded conditions will be set (if an overflow occurred) so these are cleared in line 15.
With these changes, messages are displayed and the main function ended if either a bad input parameter word was found (for example, try passing a non-numeric word) or if the calculation could not be completed (e.g., try a value for the third argument which is not an integer).[1]
When one of the decNumber functions sets a bit in the context status, the bit is compared with the corresponding bit in the traps field. If that bit is set (is 1) then a C Floating-Point Exception signal (SIGFPE) is raised. At that point, a signal handler function (previously identified to the C runtime) is called.
The signal handler function can either simply log or report the trap and then return (and execution will continue as though the trap had not occurred) or – as in this example – it can call the C longjmp function to jump to a previously preserved point of execution.
Note that if a jump is used, control will not return to the code which called the decNumber function that raised the trap, and so care must be taken to ensure that any resources in use (such as allocated memory) are cleaned up appropriately.
To create this example, modify the Example 1 code this time, by first removing line 18 (set.traps=0;). This will leave the traps field with its default setting, which has all the DEC_Errors bits set, hence enabling traps for any of those conditions. Then insert after line 6 (before the main function):
1. #include <signal.h> // signal handling 2. #include <setjmp.h> // setjmp/longjmp 3. 4. jmp_buf preserve; // stack snapshot 5. 6. void signalHandler(int sig) { 7. signal(SIGFPE, signalHandler); // re-enable 8. longjmp(preserve, sig); // branch to preserved point 9. }Here, lines 1 and 2 include definitions for the C library functions we will use. Line 4 declares a global buffer (accessible to both the main function and the signal handler) which is used to preserve the point of execution to which we will jump after handling the signal.
Lines 6 through 9 are the signal handler. Line 7 re-enables the signal handler, as described below (in this example this is in fact unnecessary as we will be ending the program immediately). This is normally needed as handlers are disabled on entry, and need to be re-enabled if more than one trap is to be handled.
Line 8 jumps to the point preserved when the program starts up (in the next code insert). The value, sig, which the signal handler receives is passed to the preserved code. In this example, sig always has the value SIGFPE, but in a more complicated program the same signal handler could be used to handle other signals, too.
The next segment of code is inserted after line 11 of Example 1 (just after the existing declarations):
1. int value; // work variable 2. 3. signal(SIGFPE, signalHandler); // set up signal handler 4. value=setjmp(preserve); // preserve and test environment 5. if (value) { // (non-0 after longjmp) 6. set.status &= DEC_Errors; // keep only errors 7. printf("Signal trapped [%s].\n", decContextStatusToString(&set)); 8. return 2; 9. }Here, a work variable is declared in line 1 and the signal handler function is registered (identified to the C run time) in line 3. The call to the signal function identifies the signal to be handled (SIGFPE) and the function (signalHandler) that will be called when the signal is raised, and enables the handler.
Next, in line 4, the setjmp function is called. On its first call, this saves the current point of execution into the preserve variable and then returns 0. The following lines (5–8) are then not executed and execution of the main function continues as before.
If a trap later occurs (for example, if one of the arguments is not a number) then the following takes place:
The decimal32, decimal64, and decimal128 forms are provided as efficient, formats used for storing numbers of up to 7, 16 or 34 decimal digits respectively, in 4, 8, or 16 bytes. These formats are similar to, and are used in the same manner as, the C float and double data types.
Here’s an example program. Like Example 1, this is runnable as it stands, although it’s recommended that at least the argument count check be added.
1. // example5.c -- decimal64 conversions 2. #include "decimal64.h" // decimal64 and decNumber library 3. #include <stdio.h> // for (s)printf 4. 5. int main(int argc, char *argv[]) { 6. decimal64 a; // working decimal64 number 7. decNumber d; // working number 8. decContext set; // working context 9. char string[DECIMAL64_String]; // number->string buffer 10. char hexes[25]; // decimal64->hex buffer 11. int i; // counter 12. 13. decContextDefault(&set, DEC_INIT_DECIMAL64); // initialize 14. 15. decimal64FromString(&a, argv[1], &set); 16. // lay out the decimal64 as eight hexadecimal pairs 17. for (i=0; i<8; i++) { 18. sprintf(&hexes[i*3], "%02x ", a.bytes[i]); 19. } 20. decimal64ToNumber(&a, &d); 21. decNumberToString(&d, string); 22. printf("%s => %s=> %s\n", argv[1], hexes, string); 23. return 0; 24. } // mainHere, the #include on line 2 not only defines the decimal64 type, but also includes the decNumber and decContext header files. Also, if DECNUMDIGITS has not already been defined, the decimal64.h file sets it to 16 so that any decNumbers declared will be exactly the right size to take any decimal64 without rounding.
The declarations in lines 6–11 create three working structures and other work variables; the decContext structure is initialized in line 13 (here, set.traps is 0).
Line 15 converts the input argument word to a decimal64 (with a function call very similar to decNumberFromString). Note that the value would be rounded if the number needed more than 16 digits of precision.
Lines 16–19 lay out the decimal64 as eight hexadecimal pairs in a string, so that its encoding can be displayed.
Lines 20–22 show how decimal64 numbers are used. First the decimal64 is converted to a decNumber, then arithmetic could be carried out, and finally the decNumber is converted back to some standard form (in this case a string, so it can be displayed in line 22). For example, if the input argument were ‘79’, the following would be displayed on a big-endian machine:
79 => 22 38 00 00 00 00 00 79 => 79(On a little-endian machine the byte order would be reversed.)
The decimal32 and decimal128 forms are used in exactly the same way, for working with up to 7 or up to 34 digits of precision respectively. These forms have the same constants and functions as decimal64 (with the obvious name changes).
Like decimal64.h, the decimal32 and decimal128 header files define the DECNUMDIGITS constant to either 7 or 34 if it has not already been defined.
It is also possible to work with the decimal128 (etc.) formats directly, without converting to and from the decNumber format; this is much faster when only the fixed-size formats are needed. Example 7 shows how to use the decQuad module for calculations in the 128-bit format.
1. #include "decPacked.h"Then the following declarations are added to the main function:
1. uint8_t startpack[]={0x01, 0x00, 0x00, 0x0C}; // investment=100000 2. int32_t startscale=0; 3. uint8_t ratepack[]={0x06, 0x5C}; // rate=6.5% 4. int32_t ratescale=1; 5. uint8_t yearspack[]={0x02, 0x0C}; // years=20 6. int32_t yearsscale=0; 7. uint8_t respack[16]; // result, packed 8. int32_t resscale; // .. 9. char hexes[49]; // for packed->hex 10. int i; // counterThe first three pairs declare and initialize the three parameters, with a Packed Decimal byte array and associated scale for each. In practice these might be read from a file or database. The fourth pair is used to receive the result. The last two declarations (lines 9 and 10) are work variables used for displaying the result.
Next, in Example 2, line 5 is removed, and lines 14 through 26 are replaced by:
1. decPackedToNumber(startpack, sizeof(startpack), &startscale, &start); 2. decPackedToNumber(ratepack, sizeof(ratepack), &ratescale, &rate); 3. decPackedToNumber(yearspack, sizeof(yearspack), &yearsscale, &years); 4. 5. decNumberDivide(&rate, &rate, &hundred, &set); // rate=rate/100 6. decNumberAdd(&rate, &rate, &one, &set); // rate=rate+1 7. decNumberPower(&rate, &rate, &years, &set); // rate=rate**years 8. decNumberMultiply(&total, &rate, &start, &set); // total=rate*start 9. decNumberRescale(&total, &total, &mtwo, &set); // two digits please 10. 11. decPackedFromNumber(respack, sizeof(respack), &resscale, &total); 12. 13. // lay out the total as sixteen hexadecimal pairs 14. for (i=0; i<16; i++) { 15. sprintf(&hexes[i*3], "%02x ", respack[i]); 16. } 17. printf("Result: %s (scale=%ld)\n", hexes, (long int)resscale);Here, lines 1 through 3 convert the Packed Decimal parameters into decNumber structures. Lines 5-9 calculate and rescale the total, as before, and line 11 converts the final decNumber into Packed Decimal and scale. Finally, lines 13-17 lay out and display the result, which should be:
Result: 00 00 00 00 00 00 00 00 00 00 00 03 52 36 45 1c (scale=2)Note that the number is right-aligned, with a sign nibble.
1. // example7.c -- using decQuad to add two numbers together 2. 3. #include "decQuad.h" // decQuad library 4. #include <stdio.h> // for printf 5. 6. int main(int argc, char *argv[]) { 7. decQuad a, b; // working decQuads 8. decContext set; // working context 9. char string[DECQUAD String]; // number->string buffer 10. 11. if (argc<3) { // not enough words 12. printf("Please supply two numbers to add.\.n"); 13. return 1; 14. } 15. decContextDefault(&set, DEC INIT DECQUAD); // initialize 16. 17. decQuadFromString(&a, argv[1], &set); 18. decQuadFromString(&b, argv[2], &set); 19. decQuadAdd(&a, &a, &b, &set); // a=a+b 20. decQuadToString(&a, string); 21. 22. printf("%s + %s => %s\n", argv[1], argv[2], string); 23. return 0; 24. } // mainThis example is a complete, runnable program. Like Example 1, it takes two argument words, converts them to a decimal format (in this case decQuad, the 34-digit format), adds them, and converts the result back to a string for display.
Line 3 includes the decQuad header file. This in turn includes the other necessary header, decContext. The context variable set is used to set the rounding mode for the conversions from string and for the add, and its status field is used to report any errors (not checked in this example). No other field in the context is used.
To compile and run this, only the files example7.c, decContext.c, and decQuad.c are needed.
To use the 16-digit format instead of the 34-digit format, change decQuad to decDouble and QUAD to DOUBLE in the example. Note that in this case the file decQuad.c is still needed (must be compiled), because decDouble requires decQuad.
1. // example8.c -- using decQuad with the decNumber module 2. 3. #include "decQuad.h" // decQuad library 4. #include "decimal128.h" // interface to decNumber 5. #include <stdio.h> // for printf 6. 7. int main(int argc, char *argv[]) { 8. decQuad a; // working decQuad 9. decNumber numa, numb; // working decNumbers 10. decContext set; // working context 11. char string[DECQUAD String]; // number->string buffer 12. 13. if (argc<3) { // not enough words 14. printf("Please supply two numbers for power(2*a, b).\n"); 15. return 1; 16. } 17. decContextDefault(&set, DEC INIT DECQUAD); // initialize 18. 19. decQuadFromString(&a, argv[1], &set); // get a 20. decQuadAdd(&a, &a, &a, &set); // double a 21. decQuadToNumber(&a, &numa); // convert to decNumber 22. decNumberFromString(&numb, argv[2], &set); 23. decNumberPower(&numa, &numa, &numb, &set); // numa=numa**numb 24. decQuadFromNumber(&a, &numa, &set); // back via a Quad 25. decQuadToString(&a, string); // .. 26. 27. printf("power(2*%s, %s) => %s\n", argv[1], argv[2], string); 28. return 0; 29. } // mainHere, the decimal128 module is used as a ‘proxy’ between the decNumber and decQuad formats. The decimal128 and decQuad structures are identical (except in name) so pointers to the structures can safely be cast from one to the other. The decQuadToNumber and decQuadFromNumber functions are in fact macros which cast the data pointer and then use the decimal128ToNumber or decimal128FromNumber function to effect the conversion. Using a proxy in this way avoids any dependencies between decQuad and decNumber.
Note that the same decContext structure (set) is used for both decQuad and decNumber function calls. decQuad uses only the round and status fields, but decNumber also needs the other fields. All the fields are initialized by the call to decContextDefault.
The inclusion of decimal128.h also sets up the DECNUMDIGITS required and includes decNumber.h. The decimal128 module requires decimal64 (for shared code and tables), so the full list of files to compile for this example is: example8.c, decContext.c, decQuad.c, decNumber.c, decimal128.c, and decimal64.c.
[1] | Of course, in a user-friendly application, more detailed and specific error messages are appropriate. But here we are demonstrating error handling, not user interfaces. |