Bigdecimal: Variable precision decimal arithmetic C/C++ library

This software can be redistributed under GNU Lesser General Public License.
Every source code of this software can be obtained through GitHub
Copylight (c) 2018 Shigeo Kobayashi. All rights reserved.

The Bigdecimal is a variable precision decimal arithmetic C/C++ library.
Using Bigdecimal, you can obtain any number of significant digits in your computation.

This software has been tested on Windows-10(32-bit&64-bit) and Linux(32-bit CentOS-5 & 64-bit CentOS-7).
To build this software,include "bigdecimal.h" in your program and

Windows binary files(32-bit:vpc.exe,bigdecimal.dll,bigdecimal.lib) can be downloaded here.
Use VPC(Variale Precision Calculator bundled with Bigdecimal) for your testing.

Introduction

To use Bigdecimal,"bigdecimal.h" should be included in your C/C++ source program.
And,Bigdecimal variable must be declared before it is used.
To declare Bigdecimal variable, use VP_HANDLE which is defined in the bigdecimal.h.
Bigdecimal variable(VP_HANDLE) is simply a pointer to the structure Real(see bigdecimal.h) which is allocated somewhere and consists of an array of digits and other informations.
On API interface explanation of this documentation,a,b,c,and r are Bigdecimal variables(VP_HANDLE) and i,f,m,n are integers(signed or unsigned depending on the context) unless otherwise noted.

Allocation & Free

Unlike double or float in C/C++,it is noted that Bigdecimal variable(VP_HANDLE) is a container of digits with certain size.
Any Bigdecimal variable must be allocated by VpAlloc() or VpMemAlloc() before it is used,and freed(by VpFree()) when it is no more used as following example.

#include <stdio.h>
#include "bigdecimal.h"
int main(int argc, char* argv[])
{
    VP_HANDLE a = VpAlloc("+1234567890.1234567890 123456789000000E0",100);
    VpPrintF (stdout,a); /* ===> 1234567890.1234567890 123456789 */
    VpFree(&a);
    return 0;
}
Bigdecimal functionsmeaning
c = VpAlloc(szVal,m)Allocates Bigdecimal variable and sets it's value to szVal.
szVal is a character string representing any numerical value.
m is the maximum number of digits c can hold.
c = VpMemAlloc(m)Allocates Bigdecimal variable(the value is 0).
m is the maximum number of digits c can hold.
c = VpClone(a)Creates a copy of a.
c = VpLoad(c,szVal) Load the value represented by szVal(which is 'const char *') to existing vp-variable c.
And returns c,if normally processed,otherwise ERROR code.
c must have enough length to keep value given by szVal without any rounding operation, otherwise this function fails.
VpFree(&c)Frees memories allocated to c.
The first argument(st) of VpAlloc() is the string representing numeric number(initial value:+1234567890...),and the second argument is the maximum number of digits the variable 'a' can store(m).
The length of initial value has priority if the second argument's value is too small to represent it.
Bigdecimal offers VpClone(a) to clone(to create a new copy of a) the specified Bigdecimal variable.
Bigdecimal also offers VpAllocCount() which is the number of Bigdecimal variables not yet freed.

As the Bigdecimal variable is the container of digits,Bigdecimal offers two functions:
Bigdecimal functionsmeaning
m = VpMaxLength(c)m is the maximum number of digits c can store.
m is slightly bigger than the value specified by VpAlloc() or VpMemAlloc() due to internal logic.
n = VpCurLength(c)n is the number of digits currently stored in c.
VpMaxLength(c) and VpCurLength(c) return the sizes that may be slightly bigger than the actual values in it due to internal logic.
It is noted that VpMaxLength(c) >=VpCurLength(c) > 0.

Computation

Once allocated,Bigdecimal variables can be used in computation like the following program.

int main(int argc, char* argv[])
{
    VP_HANDLE a = VpAlloc("+1234567890.1234567890 123456789000000E0",100);
    VP_HANDLE b = VpAlloc("20",10);
    VP_HANDLE c = VpMemAlloc(150);
    VpPrintE(stdout,VpMul(c,a,b)); /* c=a*b ===> 0.2469135780 2469135780 246913578E11 */
    VpFree(&a);
    VpFree(&b);
    VpFree(&c);
    return 0;
}
Bigdecimal offers functions for arithmetic operations(most function returns resulting 'c' for convenience of the programming).
Only 3 functions(VpAlloc(),VpMemAlloc() and VpClone()) create new Bigdecimal variable(Other functions never do that).
It should strongly be noted that,many function(except 3 functions above) return Bigdecimal variables but they are never new ones.
For instance, VpAdd(c,a,b) computes a+b first,saves the result to c,and returns c itself.
It must be noted that the total number of digits obtained by the computation depends on the capacity of the left hand side variable.
Computation like c = a op b(op is any arithmetic operator such as +,-,*,/..), the resulting digits are being saved to c while the computation is going on.
Storing the resulting digits to c stops when the computation finishes or the capacity of c(=VpMaxLength(c)) is exhausted.
Bigdecimal functionsmeaningnotes
c = VpAsgn(c,a,op) c = a (op=1) or c = -a (op=-1)the final digit of c is rounded if VpMaxLength(c)<VpCurLength(b)
c = VpAdd(c,a,b) c = a+bthe final digit of c is rounded if VpMaxLength(c)<VpCurLength(a+b)
c = VpSub(c,a,b) c = a-bthe final digit of c is rounded if VpMaxLength(c)<VpCurLength(a-b)
c = VpMul(c,a,b) c = a*bVpMaxLength(c)>VpCurLength(a)+VpCurLength(b)*
c = VpDiv(c,r,a,b) c = a/b (r is a remainder)VpMaxLength(r)>Max(VpCurLength(a),VpMaxLength(c)+VpCurLength(b))*
f = VpCmp(a,b) 1(if a>b),0(if a==b),-1(if a<b),VP_ERROR_NON_NUMERIC(if uncomparable).return value is an integer.
c = VpFrac(c,a) copies all digits after the decimal point of a to c(|c|<1 or c=0)
c = VpFix(c,a)
c = VpInt(c,a)
copies all digits before the decimal point of a to c(|c|>=1 or c=0)
c = VpAbs(c) c = |c|
n = VpExponent(c) returns n,where c=0.xxxxxxx10**nn is an integer.
n = VpEffectiveDigits(c)returns n,where n is the number of effective digits.n is an integer for which leading and trailing 0's are not counted.
c = VpRevertSign(c)
c = VpNegate(c)
c = -c
f = VpGetSign(c) returns one of the values defined in bigdecimal.h as listed bellow :
value returned (f) c is a
VP_SIGN_NaN( 0) NaN
VP_SIGN_POSITIVE_ZERO( 1) positive zero
VP_SIGN_NEGATIVE_ZERO(-1) negative zero
VP_SIGN_POSITIVE_FINITE( 2) positive finite number
VP_SIGN_NEGATIVE_FINITE(-2) negative finite number
VP_SIGN_POSITIVE_INFINITE( 3) positive infinite number
VP_SIGN_NEGATIVE_INFINITE(-3) negative infinite number
return value is an integer.
c = VpSetSign(c,f)sets c=|c|(if f>0),c=-|c|(if f<0)f is an integer.
* VpMul() or VpDiv() may return NaN,if VpMaxLength(c) or VpMaxLength(r) does not satisfy above size limit.
* VpDiv() returns Infinity,if the denominator b is zero.
* VpDiv() returns NaN,if 0/0 is done.

Bigdecimal also offers some mathematical functions(most functions return resulting 'c').
Bigdecimal functionsmeaningnotes
c = VpSqrt(c,a) c = a1/2computed by Newton's method.
c = VpPI(c) c = 3.141592....computed by Matine's formula.
c = VpSin(c,a) c = sin(a) see Note*
c = VpCos(c,a) c = cos(a) see Note*
c = VpAtan(c,a) c = tan-1(a) |a|<=1, see Note*
c = VpExp(c,a) c = ea see Note*
c = VpLog(c,a) c = logea 0<a<= 2, see Note*
c = VpPower(c,a,n) c = an n must be an ingteger.
* In most functions,VpMaxLength(c) digits are stored to resulting c.

Note*:

Example:

#define F(H,V) printf(H);VpPrintF(stdout,V);printf("\n");
#define E(H,V) printf(H);VpPrintE(stdout,V);printf("\n");

    VP_HANDLE sqrt2 = VpMemAlloc(100);
    VP_HANDLE pi    = VpMemAlloc(100);
    VP_HANDLE pi2   = VpMemAlloc(100);
    VP_HANDLE one   = VpMemAlloc(100);
    VP_HANDLE two   = VpAlloc("2",1);
    VP_HANDLE r;
    VP_UINT   m;

    F("Sqrt(2)   =",VpSqrt(sqrt2,two));                 /* sqrt2 = sqrt(2) */
    VpFree(&two);two = VpAlloc("0",VpCurLength(sqrt2)*2+1);
    F("Sqrt(2)**2=",VpMul(two,sqrt2,sqrt2));             /* two = sqrt(2)**2  ==> 2.0 */
    F("pi        =",VpPI(pi));                           /* pi  = 3.141592...         */
    m = VpCurLength(pi)+VpCurLength(two)+1;
    if(m<VpMaxLength(pi2)) m = VpMaxLength(pi2)+1;
    r = VpAlloc("0",m);
    F("pi2       =",VpDiv(pi2,r,pi,two));                /* pi2 = pi/2 */
    E("sin(pi2)  =",VpSin(one,pi2));                     /* one = sin(pi/2)   ==> 1.0 */
    VpFree(&r);
    .........
Results:

Sqrt(2)   = 1.4142135623 7309504880 1688724209 6980785696 7187537694 8073176679 7379907324 7846210703 8850387534 3276415727
Sqrt(2)**2= 1.9999999999 9999999999 9999999999 9999999999 9999999999 9999999999 9999999999 9999999999 9999999999 9999999999 0096588757 8795506493 0439931247 0016809150 7199370597 5256343471 3223747479 7111963099 3958184437 6132938529
pi        = 3.1415926535 8979323846 2643383279 5028841971 6939937510 5820974944 5923078164 0628620899 8628034825 3421170676
pi2       = 1.5707963267 9489661923 1321691639 7514420985 8469968755 2910487472 2961539082 0314310449 9314017412 671058
sin(pi2)  = 0.9999999999 9999999999 9999999999 9999999999 9999999999 9999999999 9999999999 9999999999 9999999999 9999999991 0017E0
As series expansion is adopted for math functions,maximum iteration count can be set by the user.
Bigdecimal functionsmeaning
m = VpGetMaxIterationCount()returns maximum iteration count(default=10000)
m = VpSetMaxIterationCount(m)sets maximum iteration count to m.
n = VpGetIterationCount()returns iteration count spent by the most recent computation.

Display/Print

The computation results must be converted into human readable form.
Bigdecimal functionsmeaning
n = VpPrintE(fp,c) print c to fp in E-format.
n = VpPrintF(fp,c) print c to fp in F-format.
where fp is a stream to which the output is sent,and n is a number of characters printed.

Bigdecimal value is printed like:

E-format ==> 0.xxxxxxxxxx....Eeee
F-format ==> xxxxxxxx.yyyyyyyyyyy
where x,y and e are digits('0'-'9').

Bigdecimal value can be converted to string(character array).
Bigdecimal functionsmeaning
char *szE = VpToStringE(c,szE)szE <== E-format of c.
char *szF = VpToStringF(c,szF)szF <== F-format of c.
The array size of szE and szF must be equal to or greater than the value returned by
Bigdecimal functionsmeaning
m = VpStringLengthE(c)m is a maximum character array size to represent c in E-format.
m = VpStringLengthF(c)m is a maximum character array size to represent c in F-format.
respectively.

DigitSeparationCount
Because the output string length of the Bigdecimal can be very long,you can insert a character(DigitSeparator) in every DigitSeparationCount in output string for readablity.
This can be zero which means the string is not separated.


  VP_UINT  VpGetDigitSeparationCount();          /* default = 10 */
  VP_UINT  VpSetDigitSeparationCount(VP_UINT m);

DigitSeparator
The character to insert to output string.
This can not be '+','-', or any digit character.
This character can also be inserted to input string at any place except for exponent part for VpAlloc(),


  char  VpGetDigitSeparator();                  /* default = ' ' */
  char  VpSetDigitSeparator(char c);
DigitLeader
This can be one of '\0','+' or ' '.
This character applies for positive number.

  char  VpGetDigitLeader();                     /* default = ' ' */
  char  VpSetDigitLeader(char c);

The results of these options can be easily confirmed by using VPC.

VPC(Variable Precision Calculator) of Bigdecimal(V1)
  Copyright (c) 2018 by Shigeo Kobayashi. Allrights reserved.

Enter command
? PI a
  a= 0.3141592653 5897932384 6264338327 9502884197 1693993751 0582097494 4592307816 4062862089 9862803482 5342117063 2E1
? dc?
  10
? dc= 5
  5
? a
  a= 0.31415 92653 58979 32384 62643 38327 95028 84197 16939 93751 05820 97494 45923 07816 40628 62089 98628 03482 53421 17063 2E1
? ds?
  ' '
? ds= ,
  ','
? a
  a= 0.31415,92653,58979,32384,62643,38327,95028,84197,16939,93751,05820,97494,45923,07816,40628,62089,98628,03482,53421,17063,2E1
? dl?
  ' '
? dl= +
  '+'
? a
  a=+0.31415,92653,58979,32384,62643,38327,95028,84197,16939,93751,05820,97494,45923,07816,40628,62089,98628,03482,53421,17063,2E1
?

Rounding operation

Suppose C and A are Bigdecimal variables. The value assignment from A to C(C=A) is done by VpAsgn(C,A,1).
If VpMaxLength(C) < VpCurLength(A),then all digits of A can not be copied to C and the default rounding operation will be done.
The default round operation is done in other cases such as addition or subtraction(internal rounding).
To set or get the default rounding operation use:

Bigdecimal functionsmeaning
f = VpGetRoundMode() f is a rounding mode listed bellow.
f = VpSetRoundMode(f) f is a rounding mode listed bellow.
where f is the one of default rouding modes listed bellow.
Round modeMeaningValue
VP_ROUND_UPround away from zero.1
VP_ROUND_DOWNround towards zero(truncate).2
VP_ROUND_HALF_UPround up if the digit >= 5 otherwise truncated(default).3
VP_ROUND_HALF_DOWNround up if the digit >= 6 otherwise truncated.4
VP_ROUND_CEILINGround towards positive infinity(ceil).5
VP_ROUND_FLOORround towards negative infinity(floor).6
VP_ROUND_HALF_EVENround towards the even neighbor(Banker's rounding). 7
The position of round operation can explicitly specified by following 2 functions.
Both function return resulting c.
Bigdecimal functionsmeaning
c = VpScaleRound(c,i) i is the position of round operation mesured from decimal point.
If i>=0,then the (i+1)th digit counted from the decimal point to right direction is processed(resulting number of digits after decimal point is less than or equal to i).
If i<0,then the i-th digit counted from the decimal point to left direction is processed(at least i 0s are placed from the decimal point to left).
c = VpLengthRound(c,i) Round operation is done at the i-th position counted from the left side of the number.
Example:


    VP_HANDLE c = VpAlloc("5555555555.5555555555",1);
    VP_HANDLE a = VpMemAlloc(VpMaxLength(c));
    VpAsgn(a,c,1); F("ScaleRound ( 0)= ",VpScaleRound (c, 0));
    VpAsgn(c,a,1); F("ScaleRound ( 2)= ",VpScaleRound (c, 2));
    VpAsgn(c,a,1); F("ScaleRound (-2)= ",VpScaleRound (c,-2));
    VpAsgn(c,a,1); F("LengthRound(12)= ",VpLengthRound(c,12));
    VpAsgn(c,a,1); F("LengthRound( 8)= ",VpLengthRound(c, 8));
Results:

ScaleRound ( 0)=  5555555556
ScaleRound ( 2)=  5555555555.56
ScaleRound (-2)=  5555555600
LengthRound(12)=  5555555555.56
LengthRound( 8)=  5555555600
In above examples,the rounding operation is done by default rounding mode(VP_ROUND_HALF_UP).
You can explicitly specify rounding mode(mode) by following functions.

  VP_EXPORT(VP_HANDLE) VpAsgn2(VP_HANDLE c, VP_HANDLE a, int isw,int mode)
  VP_EXPORT(VP_HANDLE) VpScaleRound2(VP_HANDLE a, int ixRound,int mode)
  VP_EXPORT(VP_HANDLE) VpLengthRound2(VP_HANDLE a, int ixRound,int mode)

Infinity,NaN(Not a Number),Zero

Bigdecimal supports Infinity,Zero,and NaN as defined in IEEE 754.
Infinity,Zero,and NaN are specially handled in Bigdecimal.

Creation

NaN(Not a number) can be obtained by undefined computation like 0.0/0.0 or Infinity-Infinity.
Any computation including NaN results to NaN.
Comparisons with NaN never become true,including comparison with NaN itself.

Zero has two different variations as +0.0 and -0.0. But,still, +0.0==-0.0 is true.

Error handling

VpAlloc() may return non VP_HANDLE value(error code) if the first argument contains bad character or memory allocation fails.
VpMemAlloc() may also return non VP_HANDLE value if it fails to allocate memories.
Because VpAlloc() or VpMemAlloc() may return an error code,it is very important to examine the returned value or the program may crash.

To check if the returned value is valid VP_HANDLE,use VpValid() or VpInvalid() as:


   VP_HANDLE h = VpAlloc("12345+123",2); /* ==> ERROR:  + is placed at illegal position. */
   if(VpInvalid(h)) {
       printf("ERROR returned by VpAlloc()\n");
       exit(0);
   }
   ...... 
Checking on the return value is always valid.
To check the VP_HANDLE, use following macros.
macrosmeaning
VpIsInvalid(c) 1 if c is invalid,0 if c is valid VP_HANDLE.
VpIsValid(h) 0 if h is invalid,1 if h is valid VP_HANDLE.
VpIsNumeric(h) 1 if h is valid and is not NaN nor Infinity,0 of h is invalid,h is NaN or Infinity.
To set exception handler is also quite usefull(strongly recommended).

Exception handler

If memory allocation fails or any other error occures,Bigdecimal always calls exception handler if it is set by the user through VpSetExceptionHandler().
Refer to the following example.

void MyException(VP_HANDLE h,const char *msg)
{
    printf("MyException(%s)\n",msg);
}
void main(int argc, char* argv[])
{
    VpSetExceptionHandler(MyException);
    E("Err =",VpAlloc("-0..123",1)); /* ERROR => this causes MyException() is called within VpAlloc() */
}
Results:
MyException(Bad numeric string for VpAlloc())
VpPrintF():Invalid handle(1)

miscellaneous

On the following table,VpIsxxxx(c) returns 1 if the VP_HANDLE c is xxxx otherwise returns 0 where xxxx is One(1),Positive Zero(+0),Negative Zero(-0),Zero(0),NaN,Positive infinity(+Infinity), Negative infinity(-Infinity) or Infinity respectively.
And VpSetxxxx(c,[f]) set c to xxxx as same as above where f is a sign to set if there.
Bigdecimal functionsmeaning
f = VpIsOne(c)
c = VpSetOne(c)
1 if c==1
c=1
f = VpIsPosZero(c)
c = VpSetPosZero(c)
1 if c==+0
c=+0
f = VpIsNegZero(c)
c = VpSetNegZero(c)
1 if c==-0
c=-0
f = VpIsZero(c)
c = VpSetZero(c,f)
1 if c==0
c=0 (+0 if f>0, -0 if f<0))
f = VpIsNaN(c)
c = VpSetNaN(c)
1 if c== NaN
c=NaN
f = VpIsPosInf(c)
c = VpSetPosInf(c)
1 if c==+Infinity
c=+Infinity
f = VpIsNegInf(c)
c = VpSetNegInf(c)
1 if c==-Infinity
c=-Infinity
f = VpIsInf(c)
c = VpSetInf(c,f)
1 if c==Infinity
c=Infinity (+Infinity if f>0,-Infinity if f<0)

Bigdecimal structure

Bigdecimal variable structure defined in bigdecimal.h.

/*
 * VP representation
 *  r = 0.xxxxxxxxx *BASE**exponent
 */
typedef struct {
    VP_UINT   Size;    /* all byte size of this structure(used in realloc() case).  */
    VP_UINT   MaxPrec; /* Maximum precision size                          */
                       /* This is the actual size of frac[]               */
                       /*(frac[0] to frac[MaxPrec-1] are available).      */
    VP_UINT   Prec;    /* Current precision size.                         */
                       /* This indicates how much the.                    */
                       /* the array frac[] is actually used.              */
    VP_UINT   Tag;     /* Space for the user(Bigdecimal never touch this) */
                       /* Use VpSetTag() or VpGetTab() to access.         */
    int       exponent;/* Exponent part.                                  */
    int       sign;    /* Attributes of the value.                        */
                       /* See VP_SIGN_xxxxxxx defined.
                        *        ==0 : NaN
                        *          1 : Positive zero
                        *         -1 : Negative zero
                        *          2 : Positive number
                        *         -2 : Negative number
                        *          3 : Positive infinite number
                        *         -3 : Negative infinite number
                        */
    VP_DIGIT   frac[1]; /* Array of fraction part. */
} Real;

#define VP_SIGN_NaN                0 /* NaN                      */
#define VP_SIGN_POSITIVE_ZERO      1 /* Positive zero            */
#define VP_SIGN_NEGATIVE_ZERO     -1 /* Negative zero            */
#define VP_SIGN_POSITIVE_FINITE    2 /* Positive finite number   */
#define VP_SIGN_NEGATIVE_FINITE   -2 /* Negative finite number   */
#define VP_SIGN_POSITIVE_INFINITE  3 /* Positive infinite number */
#define VP_SIGN_NEGATIVE_INFINITE -3 /* Negative infinite number */
VP_DIGIT can be 32-bit or 64-bit unsigned integer(see bigdecimal.h).
On 64-bit machine with 64-bit OS,better performance can be obtained by setting VP_DIGIT as 'unsigned long long'(64-bit).
On 32-bit OS,VP_DIGIT can also be set to be 64-bit(unsigned long long) which may cause bad performance due to 64-bit emulation.

VPC(Variable Precision Calculator V2) for Bigdecimal API testing

VPC(Variale Precision Cauculator bundled with Bigdecimal) is a simple but programmable calculator for testing Bigdecimal API.
VPC v2 is completely reorganized from v1. For detail of the VPC V2,
see the VPC document.
Shigeo Kobayashi 2024-3-21