You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1363 lines
34 KiB
C++

#include <float.h>
#include <QString>
#include <QStringList>
#include <QtTest/QtTest>
#include "QDecContext.hh"
#include "QDecNumber.hh"
#include "QDecSingle.hh"
#include "QDecDouble.hh"
#include "QDecQuad.hh"
#include "QDecPacked.hh"
extern "C" {
#include "decimal64.h"
#include "decimal128.h"
#include "decPacked.h"
#include "decQuad.h"
}
#include <iostream>
using namespace std;
#include "QDecNumberTests.hh"
#if defined(__GNUC__)
# ident "$Id$"
#elif defined(__sun)
# pragma ident "$Id$"
#elif defined(_WIN32)
# pragma comment( user, __FILE__ " " __DATE__ " " __TIME__ "$Id$" )
#endif
QDebug operator<<(QDebug dbg, const QDecContext& c)
{
QString cstr;
{
QTextStream ts(&cstr);
ts << c;
}
dbg.nospace() << cstr;
return dbg.space();
}
QDecNumberTests::QDecNumberTests(const QStringList& args)
{
// These are the flags we recognize
QStringList flags;
flags << "testdir" << "testfile" << "testcase"
<< "testfilefilter";
// Generic flag format
QRegExp flagre("--(\\w+)=(.*)");
// Store flags and values in m_argsMap
for(int i=0; i<args.size(); i++) {
QString flag = args.at(i);
if(flagre.exactMatch(flag)) {
QString fkey = flagre.cap(1);
QString fval = flagre.cap(2);
m_argsMap.insert(fkey.toLower(), fval);
}
}
}
void QDecNumberTests::compound_interest()
{
decNumber one, mtwo, hundred; // constants
decNumber start, rate, years; // parameters
decNumber total; // result
decContext set; // working context
char string[DECNUMDIGITS+14]; // conversion buffer
decContextDefault(&set, DEC_INIT_BASE); // initialize
set.traps=0; // no traps
set.digits=25; // precision 25
decNumberFromString(&one, "1", &set); // set constants
decNumberFromString(&mtwo, "-2", &set);
decNumberFromString(&hundred, "100", &set);
QVERIFY(0 == set.status);
decNumberFromString(&start, "100000", &set); // parameter words
decNumberFromString(&rate, "6.5", &set);
decNumberFromString(&years, "20", &set);
decNumberDivide(&rate, &rate, &hundred, &set); // rate=rate/100
decNumberAdd(&rate, &rate, &one, &set); // rate=rate+1
decNumberPower(&rate, &rate, &years, &set); // rate=rate**years
decNumberMultiply(&total, &rate, &start, &set); // total=rate*start
decNumberRescale(&total, &total, &mtwo, &set); // two digits please
QVERIFY(0 == (set.status & DEC_Errors));
decNumberToString(&total, string);
QCOMPARE(string, "352364.51");
}
void QDecNumberTests::compressed_formats()
{
decimal64 a; // working decimal64 number
decNumber d; // working number
decContext set; // working context
char string[DECIMAL64_String]; // number>string buffer
char hexes[25]; // decimal64>hex buffer
int i; // counter
decContextDefault(&set, DEC_INIT_DECIMAL64); // initialize
decimal64FromString(&a, "79", &set);
// lay out the decimal64 as eight hexadecimal pairs
for (i=0; i<8; i++) {
sprintf(&hexes[i*3], "%02x ", a.bytes[i]);
}
decimal64ToNumber(&a, &d);
decNumberToString(&d, string);
QCOMPARE(string, "79");
// Little endian:
QCOMPARE((const char*)hexes, "79 00 00 00 00 00 38 22 ");
// Big endian:
//QCOMPARE(hexes, "22 38 00 00 00 00 00 79 ");
//printf("%s => %s=> %s\n", argv[1], hexes, string);
}
void QDecNumberTests::packed_decimals()
{
uint8_t startpack[]={0x01, 0x00, 0x00, 0x0C}; // investment=100000
int32_t startscale=0;
uint8_t ratepack[]={0x06, 0x5C}; // rate=6.5%
int32_t ratescale=1;
uint8_t yearspack[]={0x02, 0x0C}; // years=20
int32_t yearsscale=0;
uint8_t respack[16]; // result, packed
int32_t resscale; // ..
char hexes[49]; // for packed>hex
int i; // counter
decNumber one, mtwo, hundred; // constants
decNumber start, rate, years; // parameters
decNumber total; // result
decContext set; // working context
decContextDefault(&set, DEC_INIT_BASE); // initialize
set.traps=0; // no traps
set.digits=25; // precision 25
decNumberFromString(&one, "1", &set); // set constants
decNumberFromString(&mtwo, "-2", &set);
decNumberFromString(&hundred, "100", &set);
QVERIFY(0 == set.status);
decPackedToNumber(startpack, sizeof(startpack), &startscale, &start);
decPackedToNumber(ratepack, sizeof(ratepack), &ratescale, &rate);
decPackedToNumber(yearspack, sizeof(yearspack), &yearsscale, &years);
decNumberDivide(&rate, &rate, &hundred, &set); // rate=rate/100
decNumberAdd(&rate, &rate, &one, &set); // rate=rate+1
decNumberPower(&rate, &rate, &years, &set); // rate=rate**years
decNumberMultiply(&total, &rate, &start, &set); // total=rate*start
decNumberRescale(&total, &total, &mtwo, &set); // two digits please
decPackedFromNumber(respack, sizeof(respack), &resscale, &total);
// lay out the total as sixteen hexadecimal pairs
for (i=0; i<16; i++) {
sprintf(&hexes[i*3], "%02x ", respack[i]);
}
QVERIFY(resscale == 2);
QCOMPARE((const char*)hexes, "00 00 00 00 00 00 00 00 00 00 00 03 52 36 45 1c ");
//printf("Result: %s (scale=%ld)\n", hexes, (long int)resscale);
}
void QDecNumberTests::quad_tests()
{
decQuad a, b; // working decQuads
decContext set; // working context
char string[DECQUAD_String]; // number>string buffer
decContextDefault(&set, DEC_INIT_DECQUAD); // initialize
decQuadFromString(&a, "1.123456", &set);
decQuadFromString(&b, "2.111111", &set);
decQuadAdd(&a, &a, &b, &set); // a=a+b
decQuadToString(&a, string);
QCOMPARE(string, "3.234567");
//printf("%s + %s => %s\n", argv[1], argv[2], string);
}
void QDecNumberTests::quad_with_number()
{
decQuad a; // working decQuad
decNumber numa, numb; // working decNumbers
decContext set; // working context
char string[DECQUAD_String]; // number>string buffer
decContextDefault(&set, DEC_INIT_DECQUAD); // initialize
decQuadFromString(&a, "1.0", &set); // get a
decQuadAdd(&a, &a, &a, &set); // double a
decQuadToNumber(&a, &numa); // convert to decNumber
decNumberFromString(&numb, "2.0", &set);
decNumberPower(&numa, &numa, &numb, &set); // numa=numa**numb
decQuadFromNumber(&a, &numa, &set); // back via a Quad
decQuadToString(&a, string); // ..
QCOMPARE(string, "4.00");
//printf("power(2*%s, %s) => %s\n", argv[1], argv[2], string);
}
void QDecNumberTests::QDecContext_tests()
{
QDecContext dc;
dc.setDigits(15);
dc.setRound(DEC_ROUND_HALF_UP);
dc.setEmin(-999999999);
dc.setEmax(999999999);
QCOMPARE(0, (int)dc.status());
QVERIFY(0==dc.status());
QCOMPARE(DEC_ROUND_HALF_UP, dc.round());
}
void QDecNumberTests::QDecNumber_abs()
{
QDecContext cxt;
QDecNumber dcn;
cxt.setDigits(15);
cxt.setRound(DEC_ROUND_HALF_UP);
cxt.setEmax(384);
cxt.setEmin(-383);
QVERIFY(dcn.fromString("1").abs(&cxt).toString() == "1");
QVERIFY(dcn.fromString("-1").abs(&cxt).toString() == "1");
//qDebug() << "abs:" << dcn.fromString("0.00").abs(&cxt).toString();
QVERIFY(dcn.fromString("0.00").abs(&cxt).isZero());
QVERIFY(dcn.fromString("-101.5").abs(&cxt).toString() == "101.5");
cxt.setDigits(9);
QVERIFY(dcn.fromString("-56267E-5").abs(&cxt).toString() == "0.56267");
}
void QDecNumberTests::QDecNumber_add()
{
QDecContext cxt;
QDecNumber op1,op2;
cxt.setDigits(9);
cxt.setRound(DEC_ROUND_HALF_UP);
cxt.setEmax(384);
cxt.setEmin(-383);
op1.fromString("2");
op2.fromString("3");
QVERIFY(op1.add(op2,&cxt).toString() == "5");
op1.fromString("-7");
op2.fromString("2.5");
QVERIFY(op1.add(op2,&cxt).toString() == "-4.5");
op1.fromString("7000");
op2.fromString("10000e+9");
QVERIFY(op1.add(op2,&cxt).toString() == "1.00000000E+13");
}
void QDecNumberTests::QDecimal_size()
{
qDebug() << "sizeof(QDecContext)" << sizeof(QDecContext);
QVERIFY(sizeof(decContext) == sizeof(QDecContext));
qDebug() << "sizeof(QDecPacked)" << sizeof(QDecPacked);
qDebug() << "QDecNumDigits=" << QDecNumDigits;
qDebug() << "sizeof(QDecNumber)" << sizeof(QDecNumber);
qDebug() << "sizeof(QDecSingle)" << sizeof(QDecSingle);
QVERIFY(sizeof(QDecSingle)==4);
qDebug() << "sizeof(QDecDouble)" << sizeof(QDecDouble);
QVERIFY(sizeof(QDecDouble)==8);
qDebug() << "sizeof(QDecQuad)" << sizeof(QDecQuad);
QVERIFY(sizeof(QDecQuad)==16);
}
static void qDebugDouble(const char* label, double d)
{
char bfr[1024];
sprintf(bfr,"%.*g",QDecNumDigits, d);
qDebug() << label << bfr;
}
static bool qRealFuzzyCompare(double d1, double d2)
{
double delta = d1-d2;
if(delta==0.0) return true;
// We want absolute values
if(delta < 0) delta *= -1.0;
double max = (d1 > d2) ? d1 : d2;
// 1e-6 is the highest level of error margin
if(delta/max > 0.000001) {
qDebug() << "max=" << max
<< "delta=" << delta
<< "d/m=" << delta/max
<< endl;
return false; // not equal
}
return true; // equal
}
void QDecNumber_conv(const char* dblstr)
{
QDecNumber n;
QDecContext c(DEC_INIT_DECIMAL128);
double d;
const char* ns = dblstr;
char bfr[1024];
qDebug() << endl << "QDecNumber conversion tests using string" << ns;
d = strtod(ns,0);
qDebugDouble("d=", d);
sprintf(bfr,"%.*g",QDecNumDigits, d);
qDebug() << "d=" << d << bfr;
QCOMPARE(d, atof(ns));
n.fromString(ns /*,&c*/);
qDebug() << "n=" << n.toString() << n.toEngString();
if(n.isNaN())
return; // Skip rest of the tests
qDebug() << "n.toDouble()=" << n.toDouble();
qDebug() << "QString(n.toDouble())="
<< QString::number(n.toDouble(),'g',QDecNumDigits);
sprintf(bfr, "%.*g", QDecNumDigits, n.toDouble());
qDebug() << "sprintf(n.toDouble())=" << bfr;
QCOMPARE(n.toDouble(), d);
QDecNumber n2;
n2.fromDouble(d);
qDebug() << "n2=" << n2.toString();
QCOMPARE(d, n2.toDouble());
}
void QDecX_conv(const char* dblstr)
{
QDecSingle qs;
QDecDouble qd;
QDecQuad qq;
double d;
const char* ns = dblstr;
qDebug() << endl << "QDecX conversion tests using string" << ns;
d = strtod(ns,0);
qDebugDouble("d=",d);
QCOMPARE(d, atof(ns));
qs.fromString(ns);
qDebug() << "qs.fromString()=" << qs.toString();
qs.fromDouble(d);
qDebug() << "qs.fromDouble()=" << qs.toString();
qDebugDouble("qs.toDouble()=",qs.toDouble());
QVERIFY(qRealFuzzyCompare(d, qs.toDouble()));
qd.fromString(ns);
qDebug() << "qd.fromString()=" << qd.toString();
qd.fromDouble(d);
qDebug() << "qd.fromDouble()=" << qd.toString();
qDebugDouble("qd.toDouble()=",qd.toDouble());
QVERIFY(qRealFuzzyCompare(d, qd.toDouble()));
qq.fromString(ns);
qDebug() << "qq.fromString()=" << qq.toString();
qq.fromDouble(d);
qDebug() << "qq.fromDouble()=" << qq.toString();
qDebugDouble("qq.toDouble()=",qq.toDouble());
QVERIFY(qRealFuzzyCompare(d, qq.toDouble()));
}
void QDecPacked_conv(const char* dblstr)
{
QDecPacked qp;
QDecNumber n;
double d;
const char* ns = dblstr;
qDebug() << endl << "QDecPacked conversion tests using string" << ns;
d = strtod(ns,0);
qDebugDouble("d=",d);
QCOMPARE(d, atof(ns));
n.fromString(ns);
if(n.isNaN())
return; // Skip rest of the tests
qp.fromString(ns);
qDebug() << "qp.fromString()=" << qp.toString();
qDebugDouble("qp...toDouble()=",qp.toQDecNumber().toDouble());
QVERIFY(qRealFuzzyCompare(d, qp.toQDecNumber().toDouble()));
qp.fromDouble(d);
qDebug() << "qp.fromDouble()=" << qp.toString();
// fromDouble() is not precise, use the string again
qp.fromString(ns);
qDebug() << "qp.scale()=" << qp.scale()
<< "qp.length()=" << qp.length()
<< "qp.bytes()=" << qp.bytes().toHex();
QVERIFY(n == qp.toQDecNumber());
}
void QDecNumberTests::conversion()
{
const char* darr[] = {
"1", "0.123", "10.0123", "210.01234567",
"9876543210.01234567890123456789",
"1.01234567890123456789012345678901234567890123456789012345678901234567890123456789",
"x.y?",
0
};
/*
QDecNumber n;
n.fromString("1.125");
qDebug() << n.toDouble();
return;
*/
int i;
for(i=0; darr[i] != 0; ++i)
QDecNumber_conv(darr[i]);
for(i=0; darr[i] != 0; ++i)
QDecX_conv(darr[i]);
for(i=0; darr[i] != 0; ++i)
QDecPacked_conv(darr[i]);
}
void QDecNumberTests::regression()
{
{ // Issue #1
double dmax = DBL_MAX;
double dmin = DBL_MIN;
QDecDouble ddmax(dmax);
qDebug() << "dmax=" << dmax << "ddmax=" << ddmax.toString();
//QCOMPARE(ddmax.toDouble(), dmax);
QVERIFY(1);
QDecDouble ddmin(dmin);
qDebug() << "dmin=" << dmin << "ddmin=" << ddmin.toString();
//QCOMPARE(ddmin.toDouble(), dmin);
QVERIFY(1);
}
}
//
//--------------------------------------
//
void QDecNumberTests::procTestFile(const QString& filename)
{
QFile file(filename);
if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qWarning() << "Cannot open " << filename;
return;
}
else {
m_testFile = filename;
m_testLines.clear();
}
while(!file.atEnd()) {
QByteArray line = file.readLine();
m_testLines.append(line);
}
// Remove directives belonging to previous files
clearDirectivesContext();
int rv ;
QByteArray line;
QStringList tokens;
QStringListIterator si(m_testLines);
QDecContext context;
while(si.hasNext()) {
QString line = si.next();
rv = procTestLine(line , tokens);
switch(rv) {
case TC_unknown:
case TC_ignore:
case TC_comment: break;
case TC_directive:
applyTestDirective(tokens, context);
break;
case TC_test:
qDebug() << "TESTCASE: " << line.trimmed();
if(m_skipSet.contains(tokens.at(0))) {
qDebug() << "SKIP(skipSet):" << line.trimmed();
}
else if(context.digits() > QDecNumDigits) {
// Skip testcase if precision required is higher than
// QDecNumber can accommodate.
qDebug() << "SKIP(precision):" << line.trimmed();
}
else {
// No precision issue, run the test case
runTestCase(tokens, context);
}
break;
}
} // end while
qDebug() << "Number of test lines " << m_testLines.size();
}
int QDecNumberTests::procTestLine(const QString& line,
QStringList& tokens)
{
QRegExp re_space("^\\s*");
QRegExp re_comment("^(\\s*)--(.*)");
QRegExp re_directive("^([^:]+):(.+)");
QRegExp re_testop("^(.+)->(.+)");
tokens.clear();
if(re_space.exactMatch(line)) {
// Ignore empty lines
return TC_ignore;
}
if(re_comment.exactMatch(line)) {
// Ignore full comment lines
return TC_comment;
}
// Find inline comments if any
QString ln = line;
int cpos = line.indexOf("--");
if(cpos > 0) {
// If comment start is not in quotes
int qpos = line.indexOf("'");
if(!(qpos > 0 && qpos < cpos)) {
// Comment is not in quotes...
ln = line.mid(0,cpos-1); // Extract non-comment part
}
}
if(re_directive.exactMatch(ln)) {
QString keyword = re_directive.cap(1).simplified();
QString value = re_directive.cap(2).simplified();
//qDebug() << '[' << keyword << ':' << value << ']';
tokens << keyword << value;
return TC_directive;
}
else if(re_testop.exactMatch(ln)) {
// Unary/Binary test operation tokens
QRegExp tot("^\\s*(\\S+)\\s+(\\S+)\\s+('[^']+'|\\S+)\\s*(\\S*)\\s*(\\S*)\\s*->\\s*(\\S+)\\s*(.*)");
if(tot.exactMatch(ln)) {
QString id = tot.cap(1).simplified();
QString op = tot.cap(2).simplified();
// Don't trim whitespace from tokens
QString opd1 = tot.cap(3); //.simplified();
QString opd2 = tot.cap(4); //.simplified();
QString opd3 = tot.cap(5); //.simplified();
QString res = tot.cap(6).simplified();
QString cond = tot.cap(7).simplified();
tokens << id << op << opd1 << opd2 << opd3 << res << cond;
//qDebug() << "Parsed tokens: " << tokens.join("|");
return TC_test;
}
else {
qWarning() << "Cannot parse test op: " << ln;
}
}
else {
// Unidentified line
qWarning() << "Unidentified line: " << line;
}
return TC_unknown;
}
int QDecNumberTests::applyTestDirective(const QStringList& tokens, QDecContext& ctx)
{
if(tokens.size() != 2 ) {
qWarning() << "Invalid tokens:" << tokens.join(" ");
return -1;
}
QString key = tokens.at(0).toLower(); // keyword
QString val = tokens.at(1).toLower(); // value
int rv = -1; // Return value error by default
// Flags required to construct context object
bool ok;
// Clear any status value left before
ctx.zeroStatus();
//
// Mandatory directives
//
if(!key.compare("precision")) {
// No operation to be done on context
unsigned pval = val.toUInt(&ok);
// Check if conversion is ok
if(ok) {
rv = 0;
if(pval < (unsigned)QDecNumDigits)
ctx.setDigits(pval);
}
else {
qWarning() << "Precison value conversion failed: " << val;
}
}
else if(!key.compare("rounding")) {
rv = 0; // Assume success
if(val == "ceiling") {
ctx.setRound(DEC_ROUND_CEILING);
}
else if(val == "down") {
ctx.setRound(DEC_ROUND_DOWN);
}
else if(val == "floor") {
ctx.setRound(DEC_ROUND_FLOOR);
}
else if(val == "half_down") {
ctx.setRound(DEC_ROUND_HALF_DOWN);
}
else if(val == "half_even") {
ctx.setRound(DEC_ROUND_HALF_EVEN);
}
else if(val == "half_up") {
ctx.setRound(DEC_ROUND_HALF_UP);
}
else if(val == "up") {
ctx.setRound(DEC_ROUND_UP);
}
else if(val == "05up") {
ctx.setRound(DEC_ROUND_05UP);
}
else {
rv = -1;
qWarning() << "Unknown value for rounding: " << val;
}
}
else if(!key.compare("maxexponent")) {
int32_t emax = (int32_t) val.toInt(&ok, 10);
if(ok) {
rv = 0;
ctx.setEmax(emax);
}
else {
qWarning() << "Unrecognized maxexponent: " << val;
}
}
else if(!key.compare("minexponent")) {
int32_t emin = (int32_t) val.toInt(&ok, 10);
if(ok) {
rv = 0;
ctx.setEmin(emin);
}
else {
qWarning() << "Unrecognized minexponent: " << val;
}
}
//
// Optional directives
//
if(!key.compare("version")) {
// No operation for version, just store it
rv = 0;
}
else if(!key.compare("extended")) {
uint8_t ext = static_cast<uint8_t>(val.toInt(&ok));
if(ok) {
rv = 0;
ctx.setExtended(ext);
}
else
qWarning() << "Unrecognized extended: " << val;
}
else if(!key.compare("clamp")) {
uint8_t clp = static_cast<uint8_t>(val.toInt(&ok));
if(ok) {
rv = 0;
ctx.setClamp(clp);
}
else
qWarning() << "Unrecognized clamp: " << val;
}
else if(!key.compare("dectest")) {
// Process the test cases in a file
rv = 0;
//TODO: Include the file
}
// Check if keyword/value pair is recognized
if(rv != 0)
qWarning() << "Unknown keyword " << key;
else {
m_curDirectives.insert(key, val);
//qDebug() << "directive=" << key << " val=" << val;
}
//qDebug() << "ctx=" << ctx;
return rv;
}
int QDecNumberTests::getDirectivesContext(QDecContext& ctx, bool precision)
{
QMapIterator<QString, QString> i(m_curDirectives);
QStringList tokens;
while(i.hasNext()) {
i.next();
if(!precision) {
if(i.key() == "precision")
continue; // Ignore precison directives if not wanted
}
tokens.clear();
tokens << i.key() << i.value();
applyTestDirective(tokens, ctx);
}
// If precision is not wanted, pick largest exponent values
// to avoid rounding
if(!precision) {
ctx.setEmax(QDecMaxExponent);
ctx.setEmin(QDecMinExponent);
}
if(ctx.status())
qWarning() << "getDirectivesContext ctx=" << ctx.statusToString();
//qDebug() << "getDirectivesContext ctx=" << ctx;
return 0;
}
void QDecNumberTests::displayDirectivesContext()
{
QMapIterator<QString, QString> i(m_curDirectives);
qDebug() << "Currently valid context settings:";
while(i.hasNext()) {
i.next();
qDebug() << i.key() << '=' << i.value();
}
}
void QDecNumberTests::clearDirectivesContext()
{
m_curDirectives.clear();
}
inline bool is_unary_op(QString op)
{
QStringList op_list;
op_list
<< "abs"
<< "apply"
<< "class"
<< "canonical"
<< "copy"
<< "copyabs"
<< "copynegate"
<< "exp"
<< "invert"
<< "ln"
<< "log10"
<< "logb"
<< "minus"
<< "nextminus"
<< "nextplus"
<< "plus"
<< "reduce"
<< "squareroot"
<< "toSci"
<< "toEng"
<< "minus"
<< "tointegral"
<< "tointegralx"
<< "trim"
;
return op_list.contains(op, Qt::CaseInsensitive);
}
inline bool is_binary_op(QString op)
{
QStringList op_list;
op_list
<< "add"
<< "and"
<< "compare"
<< "comparesig"
<< "comparetotal"
<< "comparetotalmag"
<< "comparetotmag"
<< "copysign"
<< "divide"
<< "divideint"
<< "max"
<< "min"
<< "maxmag"
<< "minmag"
<< "multiply"
<< "nexttoward"
<< "or"
<< "power"
<< "quantize"
<< "remainder"
<< "remaindernear"
<< "rescale"
<< "rotate"
<< "samequantum"
<< "scaleb"
<< "shift"
<< "subtract"
<< "xor"
;
return op_list.contains(op, Qt::CaseInsensitive);
}
inline bool is_ternary_op(QString op)
{
QStringList op_list;
op_list
<< "fma"
;
return op_list.contains(op, Qt::CaseInsensitive);
}
// Binary operations
inline QDecNumber op_do(QString op,
QDecNumber& n1, QDecNumber& n2, QDecNumber& n3,
QDecContext& c, QString& rs)
{
//
// Unary operations
//
if("abs" == op)
return n1.abs(&c);
if("apply" == op)
return n1.plus(&c);
// canonical is similar to apply
if("canonical" == op)
return n1.plus(&c);
if("class" == op) {
enum decClass dc = n1.toClass(&c);
rs = n1.ClassToString(dc);
return n1;
}
// Copy operation modifies the callee, thus operation
// is done on unused operand
if("copy" == op)
return n2.copy(n1);
if("copyabs" == op)
return n2.copyAbs(n1);
if("copynegate" == op)
return n2.copyNegate(n1);
if("copysign" == op)
return n3.copySign(n1,n2);
if("exp" == op)
return n1.exp(&c);
if("invert" == op)
return n1.invert(&c);
if("ln" == op)
return n1.ln(&c);
if("log10" == op)
return n1.log10(&c);
if("logb" == op)
return n1.logB(&c);
if("minus" == op)
return n1.minus(&c);
if("nextminus" == op)
return n1.nextMinus(&c);
if("nextplus" == op)
return n1.nextPlus(&c);
if("plus" == op)
return n1.plus(&c);
if("reduce" == op)
return n1.reduce(&c);
if("squareroot" == op)
return n1.squareRoot(&c);
if("tosci" == op) {
rs = n1.toString().data();
return n1;
}
if("toeng" == op) {
rs = n1.toEngString().data();
return n1;
}
if("tointegral" == op)
return n1.toIntegralValue(&c);
if("tointegralx" == op)
return n1.toIntegralExact(&c);
if("trim" == op)
return n1.trim();
//
// Binary operations
//
if("add" == op)
return n1.add(n2, &c);
if("and" == op)
return n1.digitAnd(n2, &c);
if("compare" == op)
return n1.compare(n2, &c);
if("comparesig" == op)
return n1.compareSignal(n2, &c);
if("comparetotal" == op)
return n1.compareTotal(n2, &c);
if("comparetotalmag" == op ||
"comparetotmag" == op)
return n1.compareTotalMag(n2, &c);
if("divide" == op)
return n1.divide(n2, &c);
if("divideint" == op)
return n1.divideInteger(n2, &c);
if("max" == op)
return n1.max(n2, &c);
if("min" == op)
return n1.min(n2, &c);
if("maxmag" == op)
return n1.maxMag(n2, &c);
if("minmag" == op)
return n1.minMag(n2, &c);
if("multiply" == op)
return n1.multiply(n2, &c);
if("nexttoward" == op)
return n1.nextToward(n2, &c);
if("or" == op)
return n1.digitOr(n2, &c);
if("power" == op)
return n1.power(n2, &c);
if("quantize" == op)
return n1.quantize(n2, &c);
if("remainder" == op)
return n1.remainder(n2, &c);
if("remaindernear" == op)
return n1.remainderNear(n2, &c);
if("rescale" == op)
return n1.rescale(n2, &c);
if("rotate" == op)
return n1.rotate(n2, &c);
if("samequantum" == op) {
if(n1.sameQuantum(n2)) return "1";
else return "0";
}
if("scaleb" == op)
return n1.scaleB(n2, &c);
if("shift" == op)
return n1.shift(n2, &c);
if("subtract" == op)
return n1.subtract(n2, &c);
if("xor" == op)
return n1.digitXor(n2, &c);
//
// Ternary operations
//
if("fma" == op)
return n1.fma(n2, n3, &c);
qWarning() << "Unrecognized operation: " << op << endl;
return QDecNumber();
}
int QDecNumberTests::opTest(const QStringList& tokens)
{
QString id = tokens.at(0);
QString op = tokens.at(1).toLower();
QString opd1 = tokens.at(2);
QString opd2 = tokens.at(3);
QString opd3 = tokens.at(4);
QString res = tokens.at(5);
QString cond = tokens.at(6);
bool ret = false;
QDecNumber n1,n2,n3,e;
// Conversion Context - needs high precision
QDecContext cc(DEC_INIT_DECIMAL128);
// Operation Context
QDecContext oc(DEC_INIT_DECIMAL128);
QString rs; // Result String
bool op_precision_needed = false;
bool is_rs_used = false; // Is result string used?
// Skip a testcase with # as any of the operands
for(int i=2; i<=4; i++)
if(QString('#')==tokens.at(i)) {
qDebug() << "SKIP(operand#): " << tokens.join(",");
return 0;
}
// Expected result will get maximum allowable precision
cc.setEmax(QDecMaxExponent);
cc.setEmin(QDecMinExponent);
// Expected result should not be affected by current context
if(res != "?") {
ret = token2QDecNumber(res, cc, e); // Expected result
qDebug() << "cc: " << cc;
}
cc.zeroStatus(); // Clear status flag for next operation
// Apply current context to operands now
if(op=="tosci" ||
op=="toeng" ||
op=="apply") {
op_precision_needed = true;
is_rs_used = true;
res.remove(QChar('\''));
}
getDirectivesContext(cc, op_precision_needed);
ret = token2QDecNumber(opd1, cc, n1);
cc.zeroStatus(); // Clear status flag for next operation
if(is_binary_op(op) ||
is_ternary_op(op)) {
ret = token2QDecNumber(opd2, cc, n2);
cc.zeroStatus();
}
if(is_ternary_op(op)) {
ret = token2QDecNumber(opd3, cc, n3);
cc.zeroStatus();
}
// Get context directives including precision
getDirectivesContext(oc, true);
// Perform the operation, obtain the result
QDecNumber r = op_do(op,n1,n2,n3,oc,rs);
if(res=="?") {
ret = true;
if(oc.status()) {
qDebug() << "runTestCase ctx=" << oc.statusToString()
<< "flg=" << oc.statusFlags();
}
}
else {
if(op == "tosci" ||
op == "toeng" ||
op == "class" ) {
ret = (0==res.trimmed().compare(rs));
if(!ret)
// If false check the result is identical
// This is also acceptable as there might be more than
// one representation of same number
ret = r.compare(e, &oc).isZero();
}
else {
ret = r.compare(e, &oc).isZero();
if(r.isNaN() && e.isNaN()) ret = true;
}
}
qDebug() << "oc: " << oc;
if(ret) {
qDebug() << "PASS: " << tokens.join(",");
/* Uncomment to receive more information about passing test cases: */
qDebug() << "n1=" << n1.toString().data()
<< "n2=" << n2.toString().data()
<< "r="
<< (is_rs_used ? rs.toLocal8Bit().data() : r.toString().data())
<< "e=" << e.toString().data()
<< "prc=" << oc.digits()
<< "ctx=" << (oc.status() ? oc.statusToString() : 0)
<< (is_rs_used ? res + "|" + rs : (const char*)0);
return 0; // Success
}
else {
qDebug() << "FAIL: " << tokens.join(",");
qDebug() << "n1=" << n1.toString().data()
<< "n2=" << n2.toString().data()
<< "n3=" << n3.toString().data()
<< "r="
<< (is_rs_used ? rs.toLocal8Bit().data() : r.toString().data())
<< "e=" << e.toString().data()
<< "prc=" << oc.digits()
<< "ctx=" << (oc.status() ? oc.statusToString() : 0)
<< (is_rs_used ? res + "|" + rs : (const char*)0);
// Print out operation context
qDebug() << "oc: " << oc;
// Print out prevailing context settings
displayDirectivesContext();
// Uncomment this if you want to stop the test cases after failure
//qFatal("End");
return 1; // Failure
}
return 0;
}
int QDecNumberTests::runTestCase(const QStringList& tokens, const QDecContext& /* ctx */)
{
if(tokens.size() != 7) {
qWarning() << "Invalid number of tokens: " << tokens.join(",");
return 1; // Failure
}
QString op = tokens.at(1);
if(is_unary_op(op) ||
is_binary_op(op) ||
is_ternary_op(op))
return opTest(tokens);
else
qDebug() << "SKIP(unknown op): " << tokens.join(",");
return 0;
}
bool QDecNumberTests::token2QDecNumber(const QString& token, const QDecContext& ctx, QDecNumber& num)
{
QString tt = token;
// Deal with quotes, double quotes and escaped quotes
if(tt.contains("''")) {
tt.replace("''","'");
tt = tt.remove(0,1);
tt.chop(1);
}
else
tt.remove(QChar('\''));
if(tt.contains("\"\"")) {
tt.replace("\"\"","\"");
tt = tt.remove(0,1);
tt.chop(1);
}
else
tt.remove(QChar('\"'));
if(token.contains('#')) {
QRegExp expl("#([0-9a-fA-F]+)"); // explicit notation
QRegExp altn("([0-9]+)#(.+)"); // alternative notation
if(expl.exactMatch(token)) {
QString hexval = expl.cap(1); // get hex value
switch(hexval.size()) {
case 8: {
QDecSingle ds;
ds.fromHexString(hexval.toLocal8Bit().data());
num = ds.toQDecNumber();
return true;
}
case 16: {
QDecDouble dd;
dd.fromHexString(hexval.toLocal8Bit().data());
num = dd.toQDecNumber();
return true;
}
case 32: {
QDecQuad dq;
dq.fromHexString(hexval.toLocal8Bit().data());
num = dq.toQDecNumber();
return true;
}
} // end switch
} // expl.
if(altn.exactMatch(token)) {
QString fmt = altn.cap(1); // get format size
QString val = altn.cap(2); // get number value in string
uint fmtsize = fmt.toUInt();
switch(fmtsize) {
case 32: {
qDebug() << "fmt=" << fmt << "val=" << val;
QDecSingle ds(val.toLocal8Bit().data());
num = ds.toQDecNumber();
return true;
}
case 64: {
qDebug() << "fmt=" << fmt << "val=" << val;
QDecDouble dd(val.toLocal8Bit().data());
num = dd.toQDecNumber();
return true;
}
case 128: {
qDebug() << "fmt=" << fmt << "val=" << val;
QDecQuad dq(val.toLocal8Bit().data());
num = dq.toQDecNumber();
return true;
}
} // end switch
} // altn.
// '#' in a token by itself
num.fromString("NaN");
return true;
} // contains #
//qDebug() << "ctx=" << ctx;
QDecContext c(ctx);
QDecNumber tnum;
tnum.fromString(tt.toLocal8Bit().data(), &c);
num = tnum;
//TODO: Check if warning is necessary
if(c.status()) {
qDebug() << "token2QDecNumber "
<< "tkn=" << token
<< "ctx=" << c.statusToString()
<< c.statusFlags()
<< "val=" << tnum.toString();
qDebug() << "c=" << c;
}
return true;
}
bool QDecNumberTests::QDecNumber2token(QString& token, const QDecNumber& num)
{
QString numstr = num.toString();
token = numstr;
return true;
}
void QDecNumberTests::test_cases()
{
// Initiase the set of test cases to be skipped
m_skipSet << "pwsx805" << "powx4302" << "powx4303" << "powx4342"
<< "powx4343" << "lnx116" << "lnx732";
// Invalid operations due to restrictions
m_skipSet << "expx901" << "expx902" << "lnx901" << "lnx902"
<< "logx901" << "logx902" << "powx4001" << "powx4002";
// Failures due to settings of clamp, could be ignored
m_skipSet << "basx716" << "basx720" << "basx724" << "basx744";
QString cwd = QDir::currentPath() ;
// Assume test application is run from cwd
QString prjdir = cwd + "/../../../../test/";
QDir pdir(prjdir);
// If not, assume it's called from project root directory
if(!pdir.exists()) {
prjdir = cwd + "/test/";
}
// Check if user specified a test case directory
QString tdir = m_argsMap.value("testdir",
//"tc_subset");
"tc_full");
tdir = prjdir + tdir;
QString tfile = m_argsMap.value("testfile");
QString tffilter = m_argsMap.value("testfilefilter");
try {
qDebug() << "Locating test data from directory " << tdir;
QDir dir(tdir);
if(!dir.exists()) {
qWarning() << "Cannot find test directory" << tdir;
return;
}
dir.setFilter(QDir::Files);
qDebug() << "testfilefilter=" << tffilter.toLocal8Bit();
if(tffilter.size()) {
QStringList filters;
filters << tffilter;
dir.setNameFilters(filters);
}
QStringList list = dir.entryList();
qDebug() << "Found test files: " << list;
QStringListIterator si(list);
while(si.hasNext()) {
QString f = si.next();
qDebug() << f;
procTestFile(tdir + "/" + f);
}
/*
for(i = 0; i < list.size(); ++i) {
// Skip test file is wanted and don't match with current file name
if(tfile.size() && tfile!=list[i]) {
qWarning() << "Skipping " << list[i].toLocal8Bit();
continue;
}
qDebug() << list[i].toLocal8Bit();
procTestFile(tdir + "/" + list[i]);
}
*/
QVERIFY(1);
}
catch(const char* s) {
qWarning() << "Ex " << s;
}
catch(const std::exception& e) {
qWarning() << e.what();
}
catch(...) {
qWarning() << "Unknown exception" ;
}
}