(Base One logo) .NET database and distributed computing tools

Base One Number Class
Sample of Usage Through C++

The Base One Number Class - Overview


The Number Database Field class (clsNumDbFld or "Number Class" for short) provides a simple, general purpose data type for numbers. You can use a Number Class object just as you would use a C++ datatype like double, long, etc.

Exact arithmetic involving very large (or small) numbers is efficiently supported, and calculations can still be done using the convenient, familiar arithmetic operators. The Number class also directly supports the database concept of a null number (all blank), as distinct from a number with a value of zero. (Nulls are not permitted in any C++ numeric data types.)

The Number Class is designed to hold positive and negative numbers ranging from the very small, 1 x 10**-99, to the very large, 9.999... x 10**99. The maximum precision supported is 100 (length) and a maximum scale of 100, much more than can be held by any C++ numeric data type.

With the Number Class, dynamic conversion between datatypes requires no explicit casting. An error is thrown if an illegal conversion is attempted, for example assigning a very large long value to a short int or a negative number to an unsigned. Exceptions are thrown whenever a significant truncation would take place or the number cannot sustain the required number of digits to the right of the decimal point without corrupting the number (e.g. ERR_OverflowNum, ERR_DivideByZero).

There are several ways to use the Number Class: it can be used as a stand-alone class within a general C++ application, as an integrated part of the BFC/MFC application framework, or it can be used in conjunction with an application that just makes use of the BFC Database Library classes. In addition, the Number Class is the one part of Base One's class libraries (BFC) that supports STL and Unix use.


Example - Number Class Assignment Statements

Here are samples of valid assignment statements for the class:

    clsNumDbFld numTest;
    numTest = 123;
    numTest = 1.23;
    numTest = -123.456;
    numTest = "123";
    numTest = "12345678901234567890134567890";
    numTest = 0;  
    numTest = "0";
    numTest = "";  // null


Note about using Number Class as a string

The Number Class (clsNumDbFld) offers the convenience of allowing a number to be handled as a string, as well as a number. Using a string provides a way to specify constants that are outside the allowable range of C++ numeric constants.

Number class objects can be assigned numeric string values in standard null-terminated C format and can return numeric string values. For example, a number stored in a Number Class object can be returned as a string by doing a cast operation using MFC CString. Here's an example of initializing a Number Class with a string and then verifying that its string value is returned as entered:

clsNumDbFld numTmp( "1.12345" );
CString strTmp = "1.12345";
ASSERT( strTmp != (CString) numTmp );
          // Never asserts. Always equal

This code works in the STL implementation because CString is always treated as string, the result of a typedef in the supplied String Plus class header.

Instead of CString, you can use the String Plus class (clsStrPlus), which is designed to work with MFC's CString or STL's string. The String Plus class adds convenient conversion and assignment routines, trimming, padding, concatenation etc. Here's an example, that handles insignificant left and right 0's correctly:

clsNumDbFld numTmp( "1.123456789" );
clsStrPlus strTmp = "01.12345679000";
          // insignifcant 0's stripped
ASSERT( strTmp != (clsStrPlus) numTmp ); 
          // Never asserts. Always equal


Sample program to perform Number Class operations

// C++ test program for Number Class

void DoNumTest() {
    clsNumDbFld numTmp( "1.123456789" );

    short       shTmp;
    USHORT      ushTmp;
    int         iTmp;
    UINT        uiTmp;
    long        lTmp;
    ULONG       ulTmp;
    double      dTmp;
    LPCSTR      ptrTmp;
    CString     str;
    clsStrPlus  strTmp;
    clsValDbFld valTmp;

/*
Following checks make sure that the comparison functions are invoked properly
by converting the other data type to clsNumDbFld, instead of clsNumDbFld
being converted to the other datatype, in which case the assertions would fail
because of data truncation */
    shTmp = 1;
    ASSERT( shTmp == (short) (double) numTmp );
    ASSERT( !(shTmp == numTmp) );
    ASSERT( !(numTmp == shTmp) );
    ASSERT( shTmp != numTmp );
    ASSERT( numTmp != shTmp );
    ASSERT( shTmp <= numTmp );
    ASSERT( !(numTmp <= shTmp) );
    ASSERT( numTmp >= shTmp );
    ASSERT( !(shTmp >= numTmp) );

    ushTmp = 1;
    ASSERT( ushTmp == (USHORT) (double) numTmp );
    ASSERT( !(ushTmp == numTmp) );
    ASSERT( !(numTmp == ushTmp) );
    ASSERT( ushTmp != numTmp );
    ASSERT( numTmp != ushTmp );
    ASSERT( ushTmp <= numTmp );
    ASSERT( !(numTmp <= ushTmp) );
    ASSERT( numTmp >= ushTmp );
    ASSERT( !(ushTmp >= numTmp) );

    iTmp = 1;
    ASSERT( iTmp == (int) (double) numTmp );
    ASSERT( !(iTmp == numTmp) );
    ASSERT( !(numTmp == iTmp) );
    ASSERT( iTmp != numTmp );
    ASSERT( numTmp != iTmp );
    ASSERT( iTmp <= numTmp );
    ASSERT( !(numTmp <= iTmp) );
    ASSERT( numTmp >= iTmp );
    ASSERT( !(iTmp >= numTmp) );

    uiTmp = 1;
    ASSERT( uiTmp == (UINT) (double) numTmp );
    ASSERT( !(uiTmp == numTmp) );
    ASSERT( !(numTmp == uiTmp) );
    ASSERT( uiTmp != numTmp );
    ASSERT( numTmp != uiTmp );
    ASSERT( uiTmp <= numTmp );
    ASSERT( !(numTmp <= uiTmp) );
    ASSERT( numTmp >= uiTmp );
    ASSERT( !(uiTmp >= numTmp) );

    lTmp = 1;
    ASSERT( lTmp == (long) (double) numTmp );
    ASSERT( !(lTmp == numTmp) );
    ASSERT( !(numTmp == lTmp) );
    ASSERT( lTmp != numTmp );
    ASSERT( numTmp != lTmp );
    ASSERT( lTmp <= numTmp );
    ASSERT( !(numTmp <= lTmp) );
    ASSERT( numTmp >= lTmp );
    ASSERT( !(lTmp >= numTmp) );

    ulTmp = 1;
    ASSERT( ulTmp == (ULONG)(double)numTmp );
    ASSERT( !(ulTmp == numTmp) );
    ASSERT( !(numTmp == ulTmp) );
    ASSERT( ulTmp != numTmp );
    ASSERT( numTmp != ulTmp );
    ASSERT( ulTmp <= numTmp );
    ASSERT( !(numTmp <= ulTmp) );
    ASSERT( numTmp >= ulTmp );
    ASSERT( !(ulTmp >= numTmp) );

/*
If the double datatype is converted to clsNumDbFld, it gets rounded properly. 
On the other hand if clsNumDbFld is converted to double, it should yield the
rounded value and hence should be different than the original double value */
    dTmp = 1.1234567891;
    ASSERT( dTmp != (double) numTmp );
    ASSERT( dTmp == numTmp );
    ASSERT( numTmp == dTmp );
    ASSERT( !(dTmp != numTmp) );
    ASSERT( !(numTmp != dTmp) );
    ASSERT( dTmp <= numTmp );
    ASSERT( numTmp <= dTmp );
    ASSERT( numTmp >= dTmp );
    ASSERT( dTmp >= numTmp );

/*
Following checks make sure that the string data type is converted to
clsNumDbFld instead of the other way in which case the leading and trailing
zeroes can cause the string comparison to fail resulting in assert failure. */
    strTmp = "01.12345679000";
    ASSERT( strTmp != (clsStrPlus) numTmp );
    ASSERT( strTmp == numTmp );
    ASSERT( numTmp == strTmp );
    ASSERT( !(strTmp != numTmp) );
    ASSERT( !(numTmp != strTmp) );
    ASSERT( strTmp <= numTmp );
    ASSERT( numTmp <= strTmp );
    ASSERT( numTmp >= strTmp );
    ASSERT( strTmp >= numTmp );

    ptrTmp = strTmp;
    ASSERT( ptrTmp != (clsStrPlus) numTmp );
    ASSERT( ptrTmp == numTmp );
    ASSERT( numTmp == ptrTmp );
    ASSERT( !(ptrTmp != numTmp) );
    ASSERT( !(numTmp != ptrTmp) );
    ASSERT( ptrTmp <= numTmp );
    ASSERT( numTmp <= ptrTmp );
    ASSERT( numTmp >= ptrTmp );
    ASSERT( ptrTmp >= numTmp );

    str = strTmp;
    ASSERT( str != (CString) numTmp );
    ASSERT( str == numTmp );
    ASSERT( numTmp == str );
    ASSERT( !(str != numTmp) );
    ASSERT( !(numTmp != str) );
    ASSERT( str <= numTmp );
    ASSERT( numTmp <= str );
    ASSERT( numTmp >= str );
    ASSERT( str >= numTmp );

    valTmp = strTmp;
    ASSERT( valTmp != (clsValDbFld) numTmp );
    ASSERT( valTmp == numTmp );
    ASSERT( numTmp == valTmp );
    ASSERT( !(valTmp != numTmp) );
    ASSERT( !(numTmp != valTmp) );
    ASSERT( valTmp <= numTmp );
    ASSERT( numTmp <= valTmp );
    ASSERT( numTmp >= valTmp );
    ASSERT( valTmp >= numTmp );

    clsStrPlus  str1 = numTmp;
    clsStrPlus  str2( numTmp );
    clsValDbFld val1 = numTmp;
    clsValDbFld val2( numTmp );

    // check the assignment
    numTmp = shTmp;
    numTmp = ushTmp;
    numTmp = iTmp;
    numTmp = uiTmp;
    numTmp = lTmp;
    numTmp = ulTmp;
    numTmp = dTmp;
    numTmp = "1.01";    // LPCSTR
    numTmp = str;
    numTmp = strTmp;
    numTmp = valTmp;

    // check the conversion
    numTmp = 1;     // initalize numTmp with a valid signed/unsigned value

    shTmp   = numTmp;
    ushTmp  = numTmp;
    iTmp    = numTmp;
    uiTmp   = numTmp;
    lTmp    = numTmp;
    ulTmp   = numTmp;
    dTmp    = numTmp;
    str     = numTmp;
    strTmp  = numTmp;
    valTmp  = numTmp;

    // check unary '-' and '+'
    ASSERT( (double) -numTmp == -dTmp );
    ASSERT( (double) +numTmp == +dTmp );
  
    int         i;
    long        lMin = -1000;
    long        lMax = 100000;
    double      dRand1, dRand2;
    double      d1, d2, dResult;
    clsNumDbFld num1, num2;
    clsNumDbFld numResult( MAX_NumDbFldLen, 8 );
    clsNumDbFld numResult4( NUM_DbFldLenDefault, 4 );

    for (i = 0; i < 2; i++)
    {
        dRand1 = gFn_RandDouble( lMin, lMax );
        dRand2 = gFn_RandDouble( lMin, lMax );

        // check addition
        num1 = dRand1;
        num2 = dRand2;
        numResult = num1 + num2;
        d1 = dRand1;
        d2 = dRand2;
        dResult = d1 + d2;
        // upto 8 decimal places the result should be same
        ASSERT( fabs( (double) numResult - dResult ) < 0.00000001 );

        // check subtraction
        num1 = dRand1;
        num2 = dRand2;
        numResult = num1 - num2;
        d1 = dRand1;
        d2 = dRand2;
        dResult = d1 - d2;
        // upto 8 decimal places the result should be same
        ASSERT( fabs( (double) numResult - dResult ) < 0.00000001 );

        // Assign values rounded to 8 decimal places
        dRand1 = num1;
        dRand2 = num2;

        // check multiplication
        num1 = dRand1;
        num2 = dRand2;
        numResult = num1 * num2;
        d1 = dRand1;
        d2 = dRand2;
        dResult = d1 * d2;
        // upto 4 decimal places the result should be same
        ASSERT( fabs( (double) numResult - dResult ) < 0.0001 );

        // check division
        num1 = dRand1;
        num2 = dRand2;
        numResult = num1 / num2;
        d1 = dRand1;
        d2 = dRand2;
        dResult = d1 / d2;
        // upto 8 decimal places the result should be same
        ASSERT( fabs( (double) numResult - dResult ) < 0.00000001 );

        // check quotient
        num1.FindQuotientAndRemainder( num2, &numResult );
        // quotient should be same
        ASSERT( (double) numResult == (long) dResult );

        // check remainder
        dResult = fmod(d1, d2);
        numResult = num1 % num2;
        // upto 8 decimal places the result should be same
        ASSERT( fabs( (double) numResult - dResult ) < 0.00000001 );

        // check exponentiation
        d1 = dRand1 - (long) dRand1; // get the decimal part

        // let the number have 1 digit to the left of decimal point
        d1 += (long) dRand1 % 10;

        // if abs( d1 ) < 1.0, increment d1
        if (d1 > 0.0 && d1 < 1.0)
            d1++;
        else if (d1 < 0.0 && d1 > -1.0)
            d1--;

        // get the random power to which d1 would be raised
        d2 = (long) dRand2 % 90;

        numResult = d1;
        numResult = numResult.pow( (long) d2 );
        dResult = d1;
        dResult = pow( dResult, (long) d2 );
 
        // compare exponentiation results
        if (fabs( dResult ) > 1.0)
            dResult = (double) numResult / dResult;
        else
            dResult = fabs( (double) numResult - dResult ) + 1.0;
        // The results should be approximately the same
        ASSERT( dResult < 1.0001 && dResult > 0.9999 );


        // initialize numResult
        numResult = num1;
        dResult = numResult;    // initialize dResult to the same value

        // check Pre & Post increment
        // upto 8 decimal places the result should be same
        ASSERT( fabs( (double) (++numResult) - (++dResult) ) < 0.00000001 );
        // upto 8 decimal places the result should be same
        ASSERT( fabs( (double) (numResult++) - (dResult++) ) < 0.00000001 );

        // check Pre & Post decrement
        // upto 8 decimal places the result should be same
        ASSERT( fabs( (double) (--numResult) - (--dResult)) < 0.00000001 );
        // upto 8 decimal places the result should be same
        ASSERT( fabs( (double) (numResult--) - (dResult--)) < 0.00000001 );

        // check absolute value function
        // upto 8 decimal places the result should be same
        ASSERT( fabs( (double) (numResult.abs()) - fabs (dResult) )
          < 0.00000001 );

        // check Get Integer & Decimal functions
        ASSERT( (double) numResult.GetIntegerPart() == (long) dResult );
        // upto 8 decimal places the result should be same
        ASSERT( fabs( (double) numResult.GetDecimalPart() -
          (dResult - (long) dResult) ) < 0.00000001 );

        // check rounding logic (using addition)
        numResult = num1 + num2;
        numResult4 = num1;
        numResult4 = numResult4 + num2;
        // upto 4 decimal places the result should be same
        ASSERT( fabs( (double) numResult4 - (double) numResult ) < 0.0001 );

        numResult4 = num1;
        numResult4 += num2;
        // upto 4 decimal places the result should be same
        ASSERT( fabs( (double) numResult4 - (double) numResult ) < 0.0001 );

        // check rounding logic (using subtraction)
        numResult = num1 - num2;
        numResult4 = num1;
        numResult4 = numResult4 - num2;
        // upto 4 decimal places the result should be same
        ASSERT( fabs( (double) numResult4 - (double) numResult ) < 0.0001 );

        numResult4 = num1;
        numResult4 -= num2;
        // upto 4 decimal places the result should be same
        ASSERT( fabs( (double) numResult4 - (double) numResult ) < 0.0001 );
    }

    // Test using large numbers
    num1.SetDbFldLenAndScale( MAX_NumDbFldLen );
    num2.SetDbFldLenAndScale( MAX_NumDbFldLen );

    // Store max. possible 9s to the left of decimal point
    num1 = 0;
    for (iTmp = 1; iTmp <= MAX_NumDbFldLen - NUM_DbFldScaleDefault; iTmp++)
    {
        num1 = (num1 * 10) + 9;
    }

    // Divide and multiply by the same number each time and check the result
    for (iTmp = 1; iTmp <= 10; iTmp++)
    {
        num2 = num1 / iTmp;
        num2 *= iTmp;

        /*
            If it is not a perfect division, after multiplication the
            result should be same upto 5 decimal places.
        */
        ASSERT( (num1 - num2) < 0.000001 );
    }

    // Store a large number in the form 987654321098765...
    num1 = 0;

    num1 *= 100000;
    num1 += 98765.0;
    num1 *= 100000;
    num1 += 43210.0;

    num1 *= 100000;
    num1 += 98765.0;
    num1 *= 100000;
    num1 += 43210.0;

    num1 *= 100000;
    num1 += 98765.0;
    num1 *= 100000;
    num1 += 43210.0;

    num1 *= 100000;
    num1 += 98765.0;
    num1 *= 100000;
    num1 += 43210.0;

    num1 *= 100000;
    num1 += 98765.0;
    num1 *= 100000;
    num1 += 43210.0;

    // Divide the number by 100 repeatedly
    for (iTmp = 1; iTmp <= 10; iTmp++)
    {
        num2 = num1 / 100;
        num2 *= 100;

        /*
            During division, the result gets rounded to the default scale
            specified by NUM_DbFldScaleDefault which is 8.
            After multiplication, the result should be same up to 8 decimal places.
        */
        ASSERT( (num1 - num2) < 0.00000001 );

        num1 = num2;
    }
}


Number Class Intro | Overview | FAQ | Representation | Algorithms | Sample Usage | Prices


Home Products Consulting Case Studies Order Contents Contact About Us

Copyright 2012, Base One International Corporation