Przeglądaj źródła

Merge pull request #336 from ghalliday/bug85049

BUG: #85094 Fix problems with precision for decimal

Reviewed-By: Richard Chapman <rchapman@hpccsystems.com>
Richard Chapman 13 lat temu
rodzic
commit
61d8ce1a9a

+ 133 - 39
common/deftype/deftype.cpp

@@ -558,17 +558,44 @@ bool CRealTypeInfo::assignableFrom(ITypeInfo *t2)
 
 //---------------------------------------------------------------------------
 
+inline unsigned cvtDigitsToLength(bool isSigned, unsigned digits)
+{
+    if (digits == UNKNOWN_LENGTH)
+        return UNKNOWN_LENGTH;
+    return isSigned ? digits/2+1 : (digits+1)/2;
+}
+
+CDecimalTypeInfo::CDecimalTypeInfo(unsigned _digits, unsigned _prec, bool _isSigned)
+: CHashedTypeInfo(cvtDigitsToLength(_isSigned, _digits))
+{
+    digits = (_digits == UNKNOWN_LENGTH) ? UNKNOWN_DIGITS : (byte)_digits;
+    prec = (_prec == UNKNOWN_LENGTH) ? UNKNOWN_DIGITS : (byte)_prec;
+    typeIsSigned = _isSigned;
+};
+
 IValue * CDecimalTypeInfo::createValueFromStack()
 {
-    void * val = malloc(length);
+    Linked<ITypeInfo> retType;
+    if ((length == UNKNOWN_LENGTH) || (length == MAX_DECIMAL_LEADING + MAX_DECIMAL_PRECISION))
+    {
+        unsigned tosDigits, tosPrecision;
+        DecClipInfo(tosDigits, tosPrecision);
+        unsigned tosLeading = tosDigits - tosPrecision;
+        if (tosLeading > MAX_DECIMAL_LEADING)
+            tosLeading = MAX_DECIMAL_LEADING;
+        if (tosPrecision > MAX_DECIMAL_PRECISION)
+            tosPrecision = MAX_DECIMAL_PRECISION;
+        Owned<ITypeInfo> newType = makeDecimalType(tosLeading+tosPrecision, tosPrecision, typeIsSigned);
+        return createDecimalValueFromStack(newType);
+    }
+
+    void * val = alloca(length);
     DecSetPrecision(getDigits(), prec);
     if (typeIsSigned)
         DecPopDecimal(val, length, prec);
     else
         DecPopUDecimal(val, length, prec);
-    IValue * ret = createDecimalValue(val, LINK(this));
-    free(val);
-    return ret;
+    return createDecimalValue(val, LINK(this));
 }
 
 IValue * CDecimalTypeInfo::castFrom(bool isSignedValue, __int64 value)
@@ -616,6 +643,8 @@ bool CDecimalTypeInfo::equals(const CTypeInfo & _other) const
 
 unsigned CDecimalTypeInfo::getStringLen(void)
 {
+    if (length == UNKNOWN_LENGTH)
+        return UNKNOWN_LENGTH;
     return (typeIsSigned ? 1 : 0) + getDigits() + (prec ? 1 : 0); // sign + digits + dot
 }
 
@@ -623,9 +652,13 @@ StringBuffer &CDecimalTypeInfo::getECLType(StringBuffer &out)
 {
     if (!typeIsSigned)
         out.append('u');
-    out.append("decimal").append((int)digits);
-    if (prec)
-        out.append("_").append((int)prec);
+    out.append("decimal");
+    if (digits != UNKNOWN_DIGITS)
+    {
+        out.append((int)digits);
+        if (prec)
+            out.append("_").append((int)prec);
+    }
     return out;
 }
 
@@ -639,6 +672,17 @@ bool CDecimalTypeInfo::assignableFrom(ITypeInfo *t2)
     return false;
 }
 
+unsigned CDecimalTypeInfo::getBitSize()
+{
+    if (digits == UNKNOWN_DIGITS)
+        return UNKNOWN_LENGTH;
+
+    if (typeIsSigned)
+        return (digits+1)*4;
+    else
+        return digits*4;
+};
+
 unsigned CDecimalTypeInfo::getCrc()
 {
     unsigned crc = CTypeInfo::getCrc();
@@ -649,6 +693,14 @@ unsigned CDecimalTypeInfo::getCrc()
 }
 
 
+void CDecimalTypeInfo::serialize(MemoryBuffer &tgt)
+{
+    CTypeInfo::serialize(tgt);
+    tgt.append(prec);
+    tgt.append(digits);
+    tgt.append(typeIsSigned);
+}
+
 //---------------------------------------------------------------------------
 bool CBoolTypeInfo::assignableFrom(ITypeInfo *t2)
 {
@@ -1910,12 +1962,12 @@ static ITypeInfo * commonUpType(CHashedTypeInfo * candidate)
 }
 
 
-extern DEFTYPE_API ITypeInfo *makeDecimalType(int len, unsigned char prec, bool isSigned)
+extern DEFTYPE_API ITypeInfo *makeDecimalType(unsigned digits, unsigned prec, bool isSigned)
 {
-    if (len < 0 || prec > len)
-        return NULL;
-
-    return commonUpType(new CDecimalTypeInfo(len, prec, isSigned));
+    assertex((digits == UNKNOWN_LENGTH) || (digits - prec <= MAX_DECIMAL_LEADING));
+    assertex((prec == UNKNOWN_LENGTH) || ((prec <= digits) && (prec <= MAX_DECIMAL_PRECISION)));
+    assertex((prec != UNKNOWN_LENGTH) || (digits == UNKNOWN_LENGTH));
+    return commonUpType(new CDecimalTypeInfo(digits, prec, isSigned));
 }
 
 
@@ -2393,26 +2445,18 @@ static ITypeInfo * getPromotedData(ITypeInfo * left, ITypeInfo * right)
     if (lLen < rLen) lLen = rLen;
     return makeDataType(lLen);
 }
-        
-static ITypeInfo * getPromotedDecimalReal(ITypeInfo * type, bool isCompare)
-{
-//  if (!isCompare)
-//      return makeRealType(8);
 
-    unsigned digits = type->getDigits();
-    unsigned prec = type->getPrecision();
-    unsigned leading = digits - prec;
+static ITypeInfo * makeUnknownLengthDecimal(bool isCompare)
+{
+    if (isCompare)
+        return makeDecimalType(UNKNOWN_LENGTH, UNKNOWN_LENGTH, true);
+    return makeDecimalType(MAX_DECIMAL_DIGITS, MAX_DECIMAL_PRECISION, true);
+}
 
-    //A bit arbitrary...extend the precision up to 16 digits?
-    //If you want control you need to add explicit casts...
-    //we could use a decimal(unknown) type instead to represent something like this,
-    //but then there are problems with the type of output(ds, { decimal-field * real });
-    if (leading < MAX_DECIMAL_LENGTH/2)
-        prec = MAX_DECIMAL_LENGTH/2;
-    else
-        prec = MAX_DECIMAL_LENGTH - leading;
 
-    return makeDecimalType(MAX_DECIMAL_LENGTH, prec, true);
+static ITypeInfo * getPromotedDecimalReal(ITypeInfo * type, bool isCompare)
+{
+    return makeUnknownLengthDecimal(isCompare);
 }
 
 static ITypeInfo * getPromotedDecimal(ITypeInfo * left, ITypeInfo * right, bool isCompare)
@@ -2422,15 +2466,21 @@ static ITypeInfo * getPromotedDecimal(ITypeInfo * left, ITypeInfo * right, bool
     if (right->getTypeCode() == type_real)
         return getPromotedDecimalReal(left, isCompare);
 
+    unsigned lDigits = left->getDigits();
+    unsigned rDigits  = right->getDigits();
+    if (lDigits == UNKNOWN_LENGTH || rDigits == UNKNOWN_LENGTH)
+        return makeDecimalType(UNKNOWN_LENGTH, UNKNOWN_LENGTH, left->isSigned() || right->isSigned());
+
+    if (isCompare)
+        return makeUnknownLengthDecimal(isCompare);
+
     unsigned lPrec = left->getPrecision();
     unsigned rPrec = right->getPrecision();
-    unsigned lInt  = left->getDigits() - lPrec;
-    unsigned rInt  = right->getDigits() - rPrec;
+    unsigned lLead  = lDigits - lPrec;
+    unsigned rLead  = rDigits - rPrec;
+    if (lLead < rLead) lLead = rLead;
     if (lPrec < rPrec) lPrec = rPrec;
-    if (lInt < rInt) lInt = rInt;
-    if (lInt + lPrec > MAX_DECIMAL_LENGTH)
-        lInt = MAX_DECIMAL_LENGTH - lPrec;
-    return makeDecimalType(lInt + lPrec, lPrec, left->isSigned() || right->isSigned());
+    return makeDecimalType(lLead + lPrec, lPrec, left->isSigned() || right->isSigned());
 }
         
 static ITypeInfo * getPromotedReal(ITypeInfo * left, ITypeInfo * right)
@@ -2529,7 +2579,7 @@ static ITypeInfo * getPromotedType(ITypeInfo * lType, ITypeInfo * rType, bool is
 {
     ITypeInfo * l = lType->queryPromotedType();
     ITypeInfo * r = rType->queryPromotedType();
-    if (l == r) 
+    if (l == r)
         return LINK(l);
 
     type_t lcode = l->getTypeCode();
@@ -2586,6 +2636,22 @@ ITypeInfo * getPromotedNumericType(ITypeInfo * l_type, ITypeInfo * r_type)
     return getPromotedType(l,r,false);
 }
 
+ITypeInfo * getPromotedAddSubType(ITypeInfo * lType, ITypeInfo * rType)
+{
+    Owned<ITypeInfo> ret = getPromotedNumericType(lType, rType);
+    if (isDecimalType(ret) && !isUnknownSize(ret) && (ret->getDigits() - ret->getPrecision() < MAX_DECIMAL_LEADING))
+        return makeDecimalType(ret->getDigits()+1, ret->getPrecision(), ret->isSigned());
+    return ret.getClear();
+}
+
+ITypeInfo * getPromotedMulDivType(ITypeInfo * lType, ITypeInfo * rType)
+{
+    Owned<ITypeInfo> ret = getPromotedNumericType(lType, rType);
+    if (isDecimalType(ret) && !isUnknownSize(ret))
+        return makeUnknownLengthDecimal(false);
+    return ret.getClear();
+}
+
 ITypeInfo * getPromotedCompareType(ITypeInfo * left, ITypeInfo * right)
 {
     ITypeInfo * promoted = getPromotedType(left, right, true);
@@ -2812,8 +2878,15 @@ extern DEFTYPE_API ITypeInfo * getRoundType(ITypeInfo * type)
 {
     if (type->getTypeCode() == type_decimal)
     {
+        unsigned olddigits = type->getDigits();
+        if (olddigits == UNKNOWN_LENGTH)
+            return LINK(type);
+
         //rounding could increase the number of digits by 1.
-        return makeDecimalType((type->getDigits()-type->getPrecision())+1, 0, type->isSigned());
+        unsigned newdigits = (olddigits - type->getPrecision())+1;
+        if (newdigits > MAX_DECIMAL_LEADING)
+            newdigits = MAX_DECIMAL_LEADING;
+        return makeDecimalType(newdigits, 0, type->isSigned());
     }
     return makeIntType(8, true);
 }
@@ -2822,12 +2895,30 @@ extern DEFTYPE_API ITypeInfo * getRoundToType(ITypeInfo * type)
 {
     if (type->getTypeCode() == type_decimal)
     {
+        unsigned olddigits = type->getDigits();
+        unsigned oldPrecision = type->getPrecision();
+        if ((olddigits == UNKNOWN_LENGTH) || (olddigits-oldPrecision == MAX_DECIMAL_LEADING))
+            return LINK(type);
         //rounding could increase the number of digits by 1.
-        return makeDecimalType(type->getDigits()+1, type->getPrecision(), type->isSigned());
+        return makeDecimalType(olddigits+1, oldPrecision, type->isSigned());
     }
     return makeRealType(8);
 }
 
+extern DEFTYPE_API ITypeInfo * getTruncType(ITypeInfo * type)
+{
+    if (type->getTypeCode() == type_decimal)
+    {
+        unsigned olddigits = type->getDigits();
+        if (olddigits == UNKNOWN_LENGTH)
+            return LINK(type);
+
+        unsigned newdigits = (olddigits - type->getPrecision());
+        return makeDecimalType(newdigits, 0, type->isSigned());
+    }
+    return makeIntType(8, true);
+}
+
 //---------------------------------------------------------------------------
 
 CCharsetInfo::~CCharsetInfo()
@@ -3428,7 +3519,10 @@ extern DEFTYPE_API ITypeInfo * deserializeType(MemoryBuffer &src)
             src.read(prec);
             src.read(digits);
             src.read(isSigned);
-            return makeDecimalType(digits, prec, isSigned); 
+
+            unsigned fulldigits = (digits == CDecimalTypeInfo::UNKNOWN_DIGITS) ? UNKNOWN_LENGTH : digits;
+            unsigned fullprec = (prec == CDecimalTypeInfo::UNKNOWN_DIGITS) ? UNKNOWN_LENGTH : prec;
+            return makeDecimalType(fulldigits, fullprec, isSigned);
         }
     case type_bitfield:
         {

+ 7 - 5
common/deftype/deftype.hpp

@@ -53,10 +53,6 @@ interface IValue;
 #define type_littleendianint    type_swapint
 #endif
 
-//These values correspond to the maximums for fields in a dataset
-//Temporary values can be larger (see nbcd.h TempDecimal for details)
-#define MAX_DECIMAL_LENGTH      32
-
 // NOTE - do not change the values here - they are also used in Clarion and stored in a database!!
 // Add new types to the end
 
@@ -257,7 +253,7 @@ extern DEFTYPE_API ITypeInfo *makeEventType();
 extern DEFTYPE_API ITypeInfo *makeAnyType();
 extern DEFTYPE_API ITypeInfo *makeType(type_t type, int size);
 extern DEFTYPE_API IEnumeratedTypeBuilder *makeEnumeratedTypeBuilder(ITypeInfo *base, aindex_t numvalues);
-extern DEFTYPE_API ITypeInfo *makeDecimalType(int size, unsigned char prec, bool isSigned);
+extern DEFTYPE_API ITypeInfo *makeDecimalType(unsigned digits, unsigned prec, bool isSigned);
 extern DEFTYPE_API ITypeInfo *makeTableType(ITypeInfo *basetype, IInterface * distributeinfo, IInterface *gloalSortinfo, IInterface * localSortInfo);
 extern DEFTYPE_API ITypeInfo *makeGroupedTableType(ITypeInfo *basetype, IInterface *groupinfo, IInterface *sortinfo);
 extern DEFTYPE_API ITypeInfo *makeRowType(ITypeInfo *basetype);
@@ -298,6 +294,9 @@ extern DEFTYPE_API ITypeInfo * getNumericType(ITypeInfo * type);
 extern DEFTYPE_API ITypeInfo * getStringType(ITypeInfo * type);
 extern DEFTYPE_API ITypeInfo * getVarStringType(ITypeInfo * type);
 extern DEFTYPE_API ITypeInfo * getPromotedType(ITypeInfo * l_type, ITypeInfo * r_type);
+extern DEFTYPE_API ITypeInfo * getPromotedAddSubType(ITypeInfo * l_type, ITypeInfo * r_type);
+extern DEFTYPE_API ITypeInfo * getPromotedMulDivType(ITypeInfo * l_type, ITypeInfo * r_type);
+extern DEFTYPE_API ITypeInfo * getPromotedDivType(ITypeInfo * l_type, ITypeInfo * r_type);
 extern DEFTYPE_API ITypeInfo * getPromotedNumericType(ITypeInfo * l_type, ITypeInfo * r_type);
 extern DEFTYPE_API unsigned getClarionResultType(ITypeInfo *type);
 extern DEFTYPE_API ITypeInfo * getAsciiType(ITypeInfo * type);
@@ -317,7 +316,9 @@ extern DEFTYPE_API bool isLittleEndian(ITypeInfo * type);
 extern DEFTYPE_API bool isDatasetType(ITypeInfo * type);
 extern DEFTYPE_API bool isSingleValuedType(ITypeInfo * type);
 inline bool isFixedSize(ITypeInfo * type) { return type && (type->getSize() != UNKNOWN_LENGTH); }
+inline bool isUnknownSize(ITypeInfo * type) { return type && (type->getSize() == UNKNOWN_LENGTH); }
 inline bool isAnyType(ITypeInfo * type) { return type && (type->getTypeCode() == type_any); }
+inline bool isDecimalType(ITypeInfo * type) { return type && (type->getTypeCode() == type_decimal); }
 
 
 //If casting a value from type before to type after is the value preserved.
@@ -347,6 +348,7 @@ extern DEFTYPE_API ITypeInfo * queryModifier(ITypeInfo * t, typemod_t modifier);
 extern DEFTYPE_API ITypeInfo * replaceChildType(ITypeInfo * type, ITypeInfo * newChild);
 extern DEFTYPE_API ITypeInfo * getRoundType(ITypeInfo * type);
 extern DEFTYPE_API ITypeInfo * getRoundToType(ITypeInfo * type);
+extern DEFTYPE_API ITypeInfo * getTruncType(ITypeInfo * type);
 
 inline bool hasConstModifier(ITypeInfo * t)      { return hasModifier(t, typemod_const); }
 inline bool hasReferenceModifier(ITypeInfo * t)  { return hasModifier(t, typemod_ref); }

+ 6 - 5
common/deftype/deftype.ipp

@@ -332,9 +332,10 @@ protected:
     bool  typeIsSigned;
 
 public:
+    enum { UNKNOWN_DIGITS = 0xff };
     IValue * createValueFromStack(void);
 
-    CDecimalTypeInfo(int _digits, unsigned char p, bool _isSigned) : CHashedTypeInfo(_isSigned ? _digits/2+1 : (_digits+1)/2) { digits = _digits; prec = p; typeIsSigned = _isSigned;};
+    CDecimalTypeInfo(unsigned _digits, unsigned _prec, bool _isSigned);
     virtual type_t getTypeCode() const { return type_decimal; };
     virtual bool isInteger() { return false; };
 
@@ -344,10 +345,10 @@ public:
     virtual IValue * castFrom(size32_t len, const char * text);
     virtual size32_t getAlignment()               { return 1; };
     virtual StringBuffer &getECLType(StringBuffer & out);
-    virtual unsigned getPrecision() { return prec; };
-    virtual unsigned getDigits()    { return digits; };
+    virtual unsigned getPrecision() { return (prec == UNKNOWN_DIGITS) ? UNKNOWN_LENGTH : prec; };
+    virtual unsigned getDigits()    { return (digits == UNKNOWN_DIGITS) ? UNKNOWN_LENGTH : digits; };
     virtual unsigned getStringLen(); 
-    virtual unsigned getBitSize()   { return typeIsSigned? (digits+1)*4 : digits*4; };
+    virtual unsigned getBitSize();
     virtual bool isSigned()         { return typeIsSigned; }
     virtual bool isScalar()                     { return true; }
     virtual const char *queryTypeName() { return "decimal"; }
@@ -355,7 +356,7 @@ public:
     virtual unsigned getHash() const;
     virtual bool equals(const CTypeInfo & other) const;
 
-    virtual void serialize(MemoryBuffer &tgt) { CTypeInfo::serialize(tgt); tgt.append(prec); tgt.append(digits); tgt.append(typeIsSigned); }
+    virtual void serialize(MemoryBuffer &tgt);
 };
 
 class CBitfieldTypeInfo : public CTypeInfo

+ 15 - 15
common/deftype/defvalue.cpp

@@ -2037,7 +2037,7 @@ DecimalValue::~DecimalValue()
 
 int DecimalValue::compare(IValue *_to)
 {
-    assertThrow(_to->queryType()==type);
+    assertThrow(_to->getTypeCode()==type_decimal);
     BcdCriticalBlock bcdBlock;
     pushDecimalValue();
     _to->pushDecimalValue();
@@ -2050,7 +2050,6 @@ IValue *DecimalValue::castTo(ITypeInfo *t)
     if (t==type)
         return LINK(this);
 
-
     type_t tc = t->getTypeCode();
     if (tc == type_any)
         return LINK(this);
@@ -2184,15 +2183,9 @@ IValue *createDecimalValue(void * val, ITypeInfo * type)
     return new DecimalValue(val, type);
 }
 
-IValue *createDecimalValue(void * val, int length, unsigned char prec, bool isSigned)
-{
-    ITypeInfo * type = makeDecimalType(length, prec, isSigned);
-    return new DecimalValue(val, type);
-}
-
 IValue *createDecimalValueFromStack(ITypeInfo * type)
 {
-    return ((CDecimalTypeInfo*)type)->createValueFromStack();
+    return static_cast<CDecimalTypeInfo*>(type)->createValueFromStack();
 }
 
 //===========================================================================
@@ -2417,7 +2410,7 @@ void appendValueToBuffer(MemoryBuffer & mem, IValue * value)
 IValue * addValues(IValue * left, IValue * right)
 {
     IValue * ret;
-    ITypeInfo * pnt = getPromotedNumericType(left->queryType(), right->queryType());
+    ITypeInfo * pnt = getPromotedAddSubType(left->queryType(), right->queryType());
     
     switch(pnt->getTypeCode())
     {
@@ -2464,7 +2457,7 @@ IValue * addValues(unsigned num, IValue * * values)
 IValue * subtractValues(IValue * left, IValue * right)
 {
     IValue * ret;
-    ITypeInfo * pnt = getPromotedNumericType(left->queryType(), right->queryType());
+    ITypeInfo * pnt = getPromotedAddSubType(left->queryType(), right->queryType());
     
     switch(pnt->getTypeCode())
     {
@@ -2500,7 +2493,7 @@ IValue * substractValues(unsigned num, IValue * * values)
 IValue * multiplyValues(IValue * left, IValue * right)
 {
     IValue * ret;
-    ITypeInfo * pnt = getPromotedNumericType(left->queryType(), right->queryType());
+    ITypeInfo * pnt = getPromotedMulDivType(left->queryType(), right->queryType());
     
     switch(pnt->getTypeCode())
     {
@@ -2535,7 +2528,7 @@ IValue * multiplyValues(unsigned num, IValue * * values)
 
 IValue * divideValues(IValue * left, IValue * right)
 {
-    ITypeInfo * pnt = getPromotedNumericType(left->queryType(), right->queryType());
+    ITypeInfo * pnt = getPromotedMulDivType(left->queryType(), right->queryType());
 
     switch(pnt->getTypeCode())
     {
@@ -2586,7 +2579,7 @@ IValue * divideValues(unsigned num, IValue * * values)
 IValue * modulusValues(IValue * left, IValue * right)
 {
     IValue * ret;
-    ITypeInfo * pnt = getPromotedNumericType(left->queryType(), right->queryType());
+    ITypeInfo * pnt = getPromotedMulDivType(left->queryType(), right->queryType());
     
     switch(pnt->getTypeCode())
     {
@@ -2773,8 +2766,15 @@ IValue * truncateValue(IValue * v)
     case type_packedint:
         return LINK(v);
     case type_real:
-    case type_decimal:
         return createTruncIntValue(v->getIntValue(), 8, true);
+    case type_decimal:
+        {
+            BcdCriticalBlock bcdBlock;
+            v->pushDecimalValue();
+            DecTruncate();
+            OwnedITypeInfo resultType = getTruncType(v->queryType());
+            return createDecimalValueFromStack(resultType);
+        }
     }
     assertThrow(false);
     return NULL;

+ 26 - 18
ecl/hql/hqlexpr.cpp

@@ -2456,26 +2456,32 @@ IHqlExpression * ensureExprType(IHqlExpression * expr, ITypeInfo * type, node_op
     {
         if (exprType)
         {
-            //cast to STRING/DATA/VARSTRING/UNICODE/VARUNICODE means ensure that the expression has this base type.
-            if ((tc == type_data) || (tc == type_qstring))
+            //Optimize away casts to unknown length if the rest of the type matches.
+            if (exprType->getTypeCode() == tc)
             {
-                if (exprType->getTypeCode() == tc)
-                    return LINK(expr);
-            }
-            else if (tc == type_unicode || tc == type_varunicode || tc == type_utf8)
-            {
-                if((exprType->getTypeCode() == tc) && (type->queryLocale() == exprType->queryLocale()))
+                //cast to STRING/DATA/VARSTRING/UNICODE/VARUNICODE means ensure that the expression has this base type.
+                if ((tc == type_data) || (tc == type_qstring))
+                {
                     return LINK(expr);
-            }
-            else if (tc == type_string || tc == type_varstring)
-            {
-                if (exprType->getTypeCode() == tc)
+                }
+                else if (tc == type_unicode || tc == type_varunicode || tc == type_utf8)
+                {
+                    if (type->queryLocale() == exprType->queryLocale())
+                        return LINK(expr);
+                }
+                else if (tc == type_string || tc == type_varstring)
                 {
                     if ((type->queryCharset() == exprType->queryCharset()) &&
                         (type->queryCollation() == exprType->queryCollation()))
                         return LINK(expr);
                 }
+                else if (tc == type_decimal)
+                {
+                    if (type->isSigned() == exprType->isSigned())
+                        return LINK(expr);
+                }
             }
+
             /*
             The following might produce better code, but it generally makes things worse.....
             if ((exprType->getSize() != UNKNOWN_LENGTH) && (isStringType(exprType) || isUnicodeType(exprType)))
@@ -2491,7 +2497,6 @@ IHqlExpression * ensureExprType(IHqlExpression * expr, ITypeInfo * type, node_op
     if (op == no_null)
         return createNullExpr(type);
 
-
     IValue * value = expr->queryValue();
     if (value && type->assignableFrom(exprType))    // this last condition is unnecessary, but changes some persist crcs if removed
     {
@@ -14531,11 +14536,14 @@ ITypeInfo * getSumAggType(ITypeInfo * argType)
     case type_decimal:
         {
             //A guess is to add 12 more digits 
-            unsigned newLen = argType->getDigits()+12;
-            if (newLen <= 32)
-                return makeDecimalType(newLen, argType->getPrecision(), argType->isSigned());
-        //could either do as decimal, or as real
-            return makeRealType(8);
+            unsigned oldDigits = argType->getDigits();
+            if (oldDigits == UNKNOWN_LENGTH)
+                return LINK(argType);
+            unsigned oldPrecision = argType->getPrecision();
+            unsigned newDigits = argType->getDigits()+12;
+            if (newDigits - oldPrecision > MAX_DECIMAL_LEADING)
+                newDigits = MAX_DECIMAL_LEADING + oldPrecision;
+            return makeDecimalType(newDigits, oldPrecision, argType->isSigned());
         }
     default:
         return LINK(argType);

+ 2 - 2
ecl/hql/hqlfold.cpp

@@ -1426,8 +1426,8 @@ IHqlExpression * foldOrExpr(IHqlExpression * expr, bool fold_x_op_not_x)
                     {
                         IHqlExpression * bLeft = lBand->queryChild(1);
                         IHqlExpression * cRight = rBand->queryChild(1);
-                        IHqlExpression * newBor = createValue(no_bor, getPromotedECLType(bLeft->queryType(), cRight->queryType()), LINK(bLeft), LINK(cRight));
-                        IHqlExpression * newBand = createValue(no_band, getPromotedECLType(aLeft->queryType(), newBor->queryType()), LINK(aLeft), newBor);
+                        IHqlExpression * newBor = createValue(no_bor, getPromotedType(bLeft->queryType(), cRight->queryType()), LINK(bLeft), LINK(cRight));
+                        IHqlExpression * newBand = createValue(no_band, getPromotedType(aLeft->queryType(), newBor->queryType()), LINK(aLeft), newBor);
                         OwnedHqlExpr newNode = createBoolExpr(no_cast, newBand);
                         if (select)
                             newNode.setown(createBoolExpr(no_select, LINK(select), newNode.getClear());

+ 2 - 0
ecl/hql/hqlgram.hpp

@@ -455,6 +455,7 @@ public:
     void checkIndexRecordType(IHqlExpression * record, unsigned numPayloadFields, bool insideNestedRecord, const attribute & errpos);
     void checkIndexRecordTypes(IHqlExpression * index, const attribute & errpos);
     void reportIndexFieldType(IHqlExpression * expr, bool isPayload, const attribute & errpos);
+    void reportUnsupportedFieldType(ITypeInfo * type, const attribute & errpos);
     void checkCaseForDuplicates(HqlExprArray & exprs, attribute &err);
     void checkOnFailRecord(IHqlExpression * expr, attribute & errpos);
     void checkAggregateRecords(IHqlExpression * expr, IHqlExpression * record, attribute & errpos);
@@ -498,6 +499,7 @@ public:
     bool isSaved(IHqlExpression * failure);
     bool okToAddSideEffects(IHqlExpression * expr);
     void processUpdateAttr(attribute & attr);
+    IHqlExpression * createArithmeticOp(node_operator op, attribute &a1, attribute &a2);
     ITypeInfo *promoteToSameType(attribute &a1, attribute &a2);
     ITypeInfo *promoteToSameType(HqlExprArray & exprs, const attribute &ea, ITypeInfo * otherType, bool allowVariableLength);
     void promoteToSameCompareType(attribute &a1, attribute &a2, node_operator op);

+ 11 - 19
ecl/hql/hqlgram.y

@@ -4962,16 +4962,12 @@ expr
                             }
                             else
                             {
-                                ITypeInfo *type = parser->checkPromoteNumericType($1, $3);
-                                $$.setExpr(createValue(no_add, type, $1.getExpr(), $3.getExpr()), $1);
+                                $$.setExpr(parser->createArithmeticOp(no_add, $1, $3), $1);
                             }
                         }
     | expr '-' expr       
                         {
-                            parser->normalizeExpression($1);
-                            parser->normalizeExpression($3);
-                            ITypeInfo *type = parser->checkPromoteNumericType($1, $3);
-                            $$.setExpr(createValue(no_sub, type, $1.getExpr(), $3.getExpr()));
+                            $$.setExpr(parser->createArithmeticOp(no_sub, $1, $3), $1);
                         }
     | expr ORDER expr   
                         {
@@ -4982,27 +4978,21 @@ expr
                         }
     | expr '*' expr       
                         {
-                            parser->normalizeExpression($1);
-                            parser->normalizeExpression($3);
-                            ITypeInfo *type = parser->checkPromoteNumericType($1, $3);
-                            $$.setExpr(createValue(no_mul, type, $1.getExpr(), $3.getExpr()));
+                            $$.setExpr(parser->createArithmeticOp(no_mul, $1, $3), $1);
                         }
     | expr '/' expr     
                         {
                             parser->normalizeExpression($1);
                             parser->normalizeExpression($3);
-                            if ($1.queryExprType()->getTypeCode() == type_int)
+                            if (!isDecimalType($1.queryExprType()) && !isDecimalType($3.queryExprType()))
                                 parser->ensureType($1, parser->defaultRealType);
-                            ITypeInfo *type = parser->checkPromoteNumericType($1, $3);
-                            $$.setExpr(createValue(no_div, type, $1.getExpr(), $3.getExpr()));
+                            $$.setExpr(parser->createArithmeticOp(no_div, $1, $3), $1);
                         }
     | expr '%' expr     {
-                            parser->normalizeExpression($1);
-                            parser->normalizeExpression($3);
-                            parser->applyDefaultPromotions($1);
-                            parser->applyDefaultPromotions($3);
                             parser->normalizeExpression($1, type_int, false);
                             parser->normalizeExpression($3, type_int, false);
+                            parser->applyDefaultPromotions($1);
+                            parser->applyDefaultPromotions($3);
                             ITypeInfo * type = parser->promoteToSameType($1, $3); // MORE _ should calculate at wider width then cast down to narrower?
                             $$.setExpr(createValue(no_modulus, type, $1.getExpr(), $3.getExpr()));
                         }
@@ -5037,6 +5027,7 @@ expr
     | expr '&' expr     {
                             parser->normalizeExpression($1);
                             parser->normalizeExpression($3);
+                            //MORE: We could could implement for decimal types.
                             if (!$1.queryExpr()->isBoolean() || !$3.queryExpr()->isBoolean())
                             {
                                 parser->normalizeExpression($1, type_int, false);
@@ -5432,8 +5423,9 @@ primexpr1
                         }
     | TRUNCATE '(' expression ')'
                         {
-                            parser->normalizeExpression($3, type_real, false);
-                            $$.setExpr(createValue(no_truncate, LINK(parser->defaultIntegralType), $3.getExpr()));
+                            parser->normalizeExpression($3, type_numeric, false);
+                            ITypeInfo * type = getTruncType($3.queryExprType());
+                            $$.setExpr(createValue(no_truncate, type, $3.getExpr()));
                         }
     | LENGTH '(' expression ')'
                         {

+ 88 - 22
ecl/hql/hqlgram2.cpp

@@ -2230,12 +2230,13 @@ _ATOM HqlGram::createUnnamedFieldName()
 
 
 /* In parms: type, value: linked */
-void HqlGram::addField(const attribute &errpos, _ATOM name, ITypeInfo *type, IHqlExpression *value, IHqlExpression *attrs)
+void HqlGram::addField(const attribute &errpos, _ATOM name, ITypeInfo *_type, IHqlExpression *value, IHqlExpression *attrs)
 {
-    Linked<ITypeInfo> expectedType = type;
+    Owned<ITypeInfo> fieldType = _type;
+    Linked<ITypeInfo> expectedType = fieldType;
     if (expectedType->getTypeCode() == type_alien)
     {
-        IHqlAlienTypeInfo * alien = queryAlienType(type);
+        IHqlAlienTypeInfo * alien = queryAlienType(fieldType);
         expectedType.setown(alien->getLogicalType());
     }
 
@@ -2244,7 +2245,7 @@ void HqlGram::addField(const attribute &errpos, _ATOM name, ITypeInfo *type, IHq
         ITypeInfo * valueType = value->queryType();
         // MORE - is this implicit or explicit?
         if (!expectedType->assignableFrom(valueType->queryPromotedType()))
-            canNotAssignTypeWarn(type,valueType,errpos);
+            canNotAssignTypeWarn(fieldType,valueType,errpos);
         if (expectedType->getTypeCode() != type_row)
         {
             IHqlExpression * newValue = ensureExprType(value, expectedType);
@@ -2253,40 +2254,55 @@ void HqlGram::addField(const attribute &errpos, _ATOM name, ITypeInfo *type, IHq
         }
     }
 
-    if (queryPropertyInList(virtualAtom, attrs) && !type->isScalar())
+    switch (fieldType->getTypeCode())
+    {
+    case type_any:
+        if (!queryTemplateContext())
+        {
+            reportUnsupportedFieldType(fieldType, errpos);
+            fieldType.set(defaultIntegralType);
+        }
+        break;
+    case type_decimal:
+        if (fieldType->getSize() == UNKNOWN_LENGTH)
+        {
+            reportWarning(ERR_BAD_FIELD_TYPE, errpos.pos, "Fields of unknown length decimal not currently supported");
+            fieldType.setown(makeDecimalType(MAX_DECIMAL_DIGITS, MAX_DECIMAL_PRECISION, fieldType->isSigned()));
+        }
+        break;
+    }
+
+    if (queryPropertyInList(virtualAtom, attrs) && !fieldType->isScalar())
         reportError(ERR_BAD_FIELD_ATTR, errpos, "Virtual can only be specified on a scalar field");
 
     if (!name)
         name = createUnnamedFieldName();
 
     checkFieldnameValid(errpos, name);
-    if(isUnicodeType(type) && (*type->queryLocale()->str() == 0))
+    if(isUnicodeType(fieldType) && (*fieldType->queryLocale()->str() == 0))
     {
         StringBuffer locale;
         _ATOM localeAtom = createLowerCaseAtom(queryDefaultLocale()->queryValue()->getStringValue(locale));
-        ITypeInfo * newType;
-        switch (type->getTypeCode())
+        switch (fieldType->getTypeCode())
         {
         case type_varunicode:
-            newType = makeVarUnicodeType(type->getStringLen(), localeAtom);
+            fieldType.setown(makeVarUnicodeType(fieldType->getStringLen(), localeAtom));
             break;
         case type_unicode:
-            newType = makeUnicodeType(type->getStringLen(), localeAtom);
+            fieldType.setown(makeUnicodeType(fieldType->getStringLen(), localeAtom));
             break;
         case type_utf8:
-            newType = makeUtf8Type(type->getStringLen(), localeAtom);
+            fieldType.setown(makeUtf8Type(fieldType->getStringLen(), localeAtom));
             break;
         default:
-            throwUnexpectedType(type);
+            throwUnexpectedType(fieldType);
         }
-        type->Release();
-        type = newType;
     }
 
-    if ((type->getSize() != UNKNOWN_LENGTH) && (type->getSize() > MAX_SENSIBLE_FIELD_LENGTH))
+    if ((fieldType->getSize() != UNKNOWN_LENGTH) && (fieldType->getSize() > MAX_SENSIBLE_FIELD_LENGTH))
         reportError(ERR_BAD_FIELD_SIZE, errpos, "Field %s is too large", name->str());
 
-    IHqlExpression *newField = createField(name, type, value, attrs);
+    IHqlExpression *newField = createField(name, fieldType.getClear(), value, attrs);
     addToActiveRecord(newField);
 }
 
@@ -3582,12 +3598,11 @@ ITypeInfo *HqlGram::checkPromoteIfType(attribute &a1, attribute &a2)
 
 ITypeInfo *HqlGram::checkPromoteNumericType(attribute &a1, attribute &a2)
 {
+    checkNumeric(a1);
+    checkNumeric(a2);
+
     ITypeInfo *t1 = a1.queryExprType();
     ITypeInfo *t2 = a2.queryExprType();
-    if (!t1 || (!isNumericType(t1) && !isAnyType(t1)))
-        reportError(ERR_EXPECTED_NUMERIC, a1, "Expected numeric expression");
-    if (!t2 || (!isNumericType(t2) && !isAnyType(t2)))
-        reportError(ERR_EXPECTED_NUMERIC, a2, "Expected numeric expression");
 
     applyDefaultPromotions(a1);
     applyDefaultPromotions(a2);
@@ -4490,6 +4505,50 @@ ITypeInfo *HqlGram::promoteToSameCompareType(attribute &a1, attribute &a2)
     return type;
 }
 
+IHqlExpression * HqlGram::createArithmeticOp(node_operator op, attribute &a1, attribute &a2)
+{
+    normalizeExpression(a1, type_numeric, false);
+    normalizeExpression(a2, type_numeric, false);
+
+    switch (op)
+    {
+    case no_add:
+    case no_sub:
+    case no_mul:
+    case no_div:
+        applyDefaultPromotions(a1);
+        applyDefaultPromotions(a2);
+        break;
+    }
+
+    ITypeInfo *t1 = a1.queryExprType();
+    ITypeInfo *t2 = a2.queryExprType();
+    Owned<ITypeInfo> type;
+    switch (op)
+    {
+    case no_add:
+    case no_sub:
+        type.setown(getPromotedAddSubType(t1, t2));
+        break;
+    case no_mul:
+    case no_div:
+        type.setown(getPromotedMulDivType(t1, t2));
+        break;
+    }
+
+    if (!type)
+        type.setown(getPromotedType(t1, t2));
+
+    if (!isDecimalType(type))
+    {
+        ensureType(a1, type);
+        ensureType(a2, type);
+    }
+
+    return createValue(op, type.getClear(), a1.getExpr(), a2.getExpr());
+}
+
+
 void HqlGram::promoteToSameCompareType(attribute &a1, attribute &a2, node_operator op)
 {
     if ((a1.queryExpr()->getOperator() == no_constant) && (a2.queryExpr()->getOperator() != no_constant) && (op != no_between))
@@ -4985,7 +5044,7 @@ void HqlGram::checkIntegerOrString(attribute & a1)
 void HqlGram::checkNumeric(attribute &a1)
 {
     ITypeInfo *t1 = a1.queryExprType();
-    if (!t1 || !isNumericType(t1))
+    if (!t1 || (!isNumericType(t1) && !isAnyType(t1)))
     {
         StringBuffer msg("Type mismatch - numeric expression expected");
         if (t1)
@@ -4993,7 +5052,7 @@ void HqlGram::checkNumeric(attribute &a1)
             msg.append("(");
             getFriendlyTypeStr(t1, msg).append(" was given)");
         }
-        reportError(ERR_TYPEMISMATCH_INTREAL, a1, "%s", msg.str());
+        reportError(ERR_EXPECTED_NUMERIC, a1, "%s", msg.str());
         a1.release().setExpr(getSizetConstant(0));
     }
 }
@@ -6583,6 +6642,13 @@ IHqlExpression * HqlGram::checkIndexRecord(IHqlExpression * record, const attrib
 }
 
 
+void HqlGram::reportUnsupportedFieldType(ITypeInfo * type, const attribute & errpos)
+{
+    StringBuffer s;
+    getFriendlyTypeStr(type, s);
+    reportError(ERR_INDEX_BADTYPE, errpos, "Fields of type %s are not currently supported", s.str());
+}
+
 void HqlGram::reportIndexFieldType(IHqlExpression * expr, bool isKeyed, const attribute & errpos)
 {
     StringBuffer s;

+ 40 - 64
ecl/hql/hqllex.l

@@ -1208,84 +1208,49 @@ QSTRING{digit}*     {
                       return(SIMPLE_TYPE); 
                     }
 DECIMAL             {
-                        /* error pattern: DECIMAL with no size */
                         setupdatepos; 
                         if (!lookup)
                             return lookupIdentifierToken(returnToken, lexer, lookup, activeState, CUR_TOKEN_TEXT);
 
-                      lexer->reportError(returnToken, ERR_ILLSIZE_DECIMAL, "Invalid size for DECIMAL type: a size must be specified");
-
-                      // recovering
-                      returnToken.setType(makeDecimalType(5,0, true)); 
+                      returnToken.setType(makeDecimalType(UNKNOWN_LENGTH,UNKNOWN_LENGTH, true));
                       return(SIMPLE_TYPE); 
                     }
-DECIMAL{digit}+_{digit}+ {
+UDECIMAL             {
                         setupdatepos; 
                         if (!lookup)
                             return lookupIdentifierToken(returnToken, lexer, lookup, activeState, CUR_TOKEN_TEXT);
 
-                      unsigned places = atoi(strchr(CUR_TOKEN_TEXT,'_')+1); 
-                      ITypeInfo *decType = makeDecimalType(atoi(CUR_TOKEN_TEXT+7), places, true);
-                      if (!decType)
-                      {
-                        lexer->reportError(returnToken, ERR_ILLSIZE_DECIMAL, "Invalid size for DECIMAL type");
-                        decType = makeDecimalType(5, 0, true);
-                      }
-                      returnToken.setType(decType); 
+                      returnToken.setType(makeDecimalType(UNKNOWN_LENGTH,UNKNOWN_LENGTH, true));
                       return(SIMPLE_TYPE); 
                     }
-DECIMAL{digit}+     { 
+(U|u)?DECIMAL{digit}+(_{digit}+)? {
                         setupdatepos; 
                         if (!lookup)
                             return lookupIdentifierToken(returnToken, lexer, lookup, activeState, CUR_TOKEN_TEXT);
 
-                      ITypeInfo *decType = makeDecimalType(atoi(CUR_TOKEN_TEXT+7), 0, true);
-                      if (!decType)
+                      bool isSigned = (CUR_TOKEN_TEXT[0] != 'U') && (CUR_TOKEN_TEXT[0] != 'u');
+                      const char * trailing = isSigned ? CUR_TOKEN_TEXT+7 : CUR_TOKEN_TEXT+8;
+                      const char * underscore = strchr(trailing,'_');
+                      unsigned digits = atoi(trailing);
+                      unsigned places = underscore ? atoi(underscore+1) : 0;
+                      if (places > digits)
                       {
-                        lexer->reportError(returnToken, ERR_ILLSIZE_DECIMAL, "Invalid size for DECIMAL type");
-                        decType = makeDecimalType(5,0, true);
+                          lexer->reportError(returnToken, ERR_ILLSIZE_DECIMAL, "Decimal type cannot have precision>digits");
+                          places = 0;
                       }
-                      returnToken.setType(decType); 
-                      return(SIMPLE_TYPE); 
-                    }
-UDECIMAL             {
-                        /* error pattern: UDECIMAL with no size */
-                        setupdatepos; 
-                        if (!lookup)
-                            return lookupIdentifierToken(returnToken, lexer, lookup, activeState, CUR_TOKEN_TEXT);
-
-                      lexer->reportError(returnToken, ERR_ILLSIZE_UDECIMAL, "Invalid size for UDECIMAL type: a size must be specified");
-
-                      // recovering
-                      returnToken.setType(makeDecimalType(5,0, true)); 
-                      return(SIMPLE_TYPE); 
-                    }
-UDECIMAL{digit}+_{digit}+ {
-                        setupdatepos; 
-                        if (!lookup)
-                            return lookupIdentifierToken(returnToken, lexer, lookup, activeState, CUR_TOKEN_TEXT);
-
-                      unsigned places = atoi(strchr(CUR_TOKEN_TEXT,'_')+1); 
-                      ITypeInfo *decType = makeDecimalType(atoi(CUR_TOKEN_TEXT+8), places, false); 
-                      if (!decType)
+                      unsigned leading = digits - places;
+                      if (leading > MAX_DECIMAL_LEADING)
                       {
-                            lexer->reportError(returnToken, ERR_ILLSIZE_UDECIMAL, "Invalid size for UDECIMAL type");
-                            decType = makeDecimalType(5,0, false);
+                          lexer->reportError(returnToken, ERR_ILLSIZE_DECIMAL, "Decimal type contains too many leading digits (>%d)", MAX_DECIMAL_LEADING);
+                          leading = MAX_DECIMAL_LEADING;
                       }
-                      returnToken.setType(decType); 
-                      return(SIMPLE_TYPE); 
-                    }   
-UDECIMAL{digit}+    { 
-                        setupdatepos; 
-                        if (!lookup)
-                            return lookupIdentifierToken(returnToken, lexer, lookup, activeState, CUR_TOKEN_TEXT);
-
-                      ITypeInfo *decType = makeDecimalType(atoi(CUR_TOKEN_TEXT+8), 0, false);
-                      if (!decType)
+                      if (places > MAX_DECIMAL_PRECISION)
                       {
-                            lexer->reportError(returnToken, ERR_ILLSIZE_UDECIMAL, "Invalid size for UDECIMAL type");
-                            decType = makeDecimalType(5,0,false);
+                          lexer->reportError(returnToken, ERR_ILLSIZE_DECIMAL, "Decimal type contains too many trailing digits (>%d)", MAX_DECIMAL_PRECISION);
+                          places = MAX_DECIMAL_LEADING;
                       }
+
+                      ITypeInfo *decType = makeDecimalType(leading+places, places, isSigned);
                       returnToken.setType(decType); 
                       return(SIMPLE_TYPE); 
                     }
@@ -1556,10 +1521,14 @@ FUNCTIONMACRO|MACRO {
 {bindigit}+(b|B)    { setupdatepos; returnToken.setInt(str2int64(CUR_TOKEN_LENGTH-1,(const char *)CUR_TOKEN_TEXT, 2)); return(INTEGER_CONST);}
 {digit}+            { setupdatepos; returnToken.setInt(str2int64(CUR_TOKEN_LENGTH, (const char *)CUR_TOKEN_TEXT, 10)); return(INTEGER_CONST);}
 {digit}+(d|D)       { 
-                        setupdatepos; 
-                        if (CUR_TOKEN_LENGTH-1 > MAX_DECIMAL_LENGTH)
-                            lexer->reportError(returnToken, ERR_ILLSIZE_DECIMAL, "Decimal constant contains too many digits (>%d)", MAX_DECIMAL_LENGTH);
-                        Owned<ITypeInfo> type = makeDecimalType(CUR_TOKEN_LENGTH-1, 0, true);
+                        setupdatepos;
+                        unsigned digits = CUR_TOKEN_LENGTH-1;
+                        if (digits > MAX_DECIMAL_LEADING)
+                        {
+                            lexer->reportError(returnToken, ERR_ILLSIZE_DECIMAL, "Decimal constant contains too many digits (>%d)", MAX_DECIMAL_LEADING);
+                            digits = MAX_DECIMAL_LEADING;
+                        }
+                        Owned<ITypeInfo> type = makeDecimalType(digits, 0, true);
                         IValue * value = type->castFrom(CUR_TOKEN_LENGTH-1, CUR_TOKEN_TEXT);
                         returnToken.setExpr(createConstant(value));
                         return(REAL_CONST);
@@ -1569,10 +1538,17 @@ FUNCTIONMACRO|MACRO {
                         const char * dot = strchr(CUR_TOKEN_TEXT, '.');
                         unsigned before = (size32_t)(dot-CUR_TOKEN_TEXT);
                         unsigned after = CUR_TOKEN_LENGTH-2 - before;
-                        if (before > MAX_DECIMAL_LENGTH)
-                            lexer->reportError(returnToken, ERR_ILLSIZE_DECIMAL, "Decimal constant contains too many integral digits (>%d)", MAX_DECIMAL_LENGTH);
-                        else if (before + after > MAX_DECIMAL_LENGTH)
-                            lexer->reportWarning(returnToken, ERR_ILLSIZE_DECIMAL, "Decimal constant may lose significant digits (>%d)", MAX_DECIMAL_LENGTH);
+                        if (before > MAX_DECIMAL_LEADING)
+                        {
+                            lexer->reportError(returnToken, ERR_ILLSIZE_DECIMAL, "Decimal constant contains too many integral digits (>%d)", MAX_DECIMAL_LEADING);
+                            before = MAX_DECIMAL_LEADING;
+                            after = 0;
+                        }
+                        if (after > MAX_DECIMAL_PRECISION)
+                        {
+                            lexer->reportWarning(returnToken, ERR_ILLSIZE_DECIMAL, "Decimal constant may lose significant digits (>%d)", MAX_DECIMAL_PRECISION);
+                            after = MAX_DECIMAL_PRECISION;
+                        }
                         Owned<ITypeInfo> type = makeDecimalType(before+after, after, true);
                         IValue * value = type->castFrom(CUR_TOKEN_LENGTH-1, CUR_TOKEN_TEXT);
                         returnToken.setExpr(createConstant(value));

+ 1 - 0
ecl/hql/hqlutil.cpp

@@ -5154,6 +5154,7 @@ bool areTypesComparable(ITypeInfo * leftType, ITypeInfo * rightType)
     case type_utf8:
         return haveCommonLocale(leftType, rightType);
     case type_data:
+    case type_decimal:
         return true;
     case type_qstring:
     case type_varstring:

+ 4 - 0
ecl/hqlcpp/hqlcatom.cpp

@@ -200,6 +200,8 @@ _ATOM DecRoundUpAtom;
 _ATOM DecRoundToAtom;
 _ATOM DecSetPrecisionAtom;
 _ATOM DecSubAtom;
+_ATOM DecTruncateAtom;
+_ATOM DecTruncateAtAtom;
 _ATOM DecValidAtom;
 _ATOM delayedAtom;
 _ATOM deleteFileAtom;
@@ -901,6 +903,8 @@ MODULE_INIT(INIT_PRIORITY_HQLATOM)
     MAKEATOM(DecRoundTo);
     MAKEATOM(DecSetPrecision);
     MAKEATOM(DecSub);
+    MAKEATOM(DecTruncate);
+    MAKEATOM(DecTruncateAt);
     MAKEATOM(DecValid);
     MAKEATOM(delayed);
     MAKEATOM(deleteFile);

+ 2 - 0
ecl/hqlcpp/hqlcatom.hpp

@@ -200,6 +200,8 @@ extern _ATOM DecRoundUpAtom;
 extern _ATOM DecRoundToAtom;
 extern _ATOM DecSetPrecisionAtom;
 extern _ATOM DecSubAtom;
+extern _ATOM DecTruncateAtom;
+extern _ATOM DecTruncateAtAtom;
 extern _ATOM DecValidAtom;
 extern _ATOM delayedAtom;
 extern _ATOM deleteFileAtom;

+ 42 - 8
ecl/hqlcpp/hqlcpp.cpp

@@ -3036,7 +3036,7 @@ void HqlCppTranslator::buildExpr(BuildCtx & ctx, IHqlExpression * expr, CHqlBoun
         doBuildExprSysFunc(ctx, expr, tgt, sqrtAtom);
         return;
     case no_truncate:
-        doBuildExprSysFunc(ctx, expr, tgt, truncateAtom);
+        doBuildExprTrunc(ctx, expr, tgt);
         return;
     case no_offsetof:
         doBuildExprOffsetOf(ctx, expr, tgt);
@@ -4132,7 +4132,10 @@ void HqlCppTranslator::createTempFor(BuildCtx & ctx, ITypeInfo * _exprType, CHql
         case type_groupedtable:
             break;
         default:
-            UNIMPLEMENTED;
+            {
+                UNIMPLEMENTED;
+                break;
+            }
         }
     }
     else if (size > MAX_SIMPLE_VAR_SIZE)
@@ -4686,7 +4689,7 @@ void HqlCppTranslator::doBuildExprCompare(BuildCtx & ctx, IHqlExpression * expr,
                 HqlExprArray args;
                 buildCachedExpr(ctx, left, lhs);
                 buildCachedExpr(ctx, right, rhs);
-                if (!isPushed(lhs) && !isPushed(rhs) && isSameBasicType(leftType, rightType))
+                if (!isPushed(lhs) && !isPushed(rhs) && isSameBasicType(lhs.queryType(), rhs.queryType()))
                 {
                     args.append(*getSizetConstant(leftType->getSize()));
                     args.append(*getPointer(lhs.expr));
@@ -5247,6 +5250,28 @@ void HqlCppTranslator::doBuildExprRound(BuildCtx & ctx, IHqlExpression * expr, C
     }
 }
 
+void HqlCppTranslator::doBuildExprTrunc(BuildCtx & ctx, IHqlExpression * expr, CHqlBoundExpr & tgt)
+{
+    IHqlExpression * arg = expr->queryChild(0);
+    switch (arg->queryType()->getTypeCode())
+    {
+    case type_decimal:
+        {
+            bindAndPush(ctx, arg);
+            HqlExprArray args;
+            callProcedure(ctx, DecTruncateAtom, args);
+            assertex(expr->queryType()->getTypeCode() == type_decimal);
+            tgt.expr.setown(createValue(no_decimalstack, expr->getType()));
+        }
+        break;
+    default:
+        {
+            doBuildExprSysFunc(ctx, expr, tgt, truncateAtom);
+            break;
+        }
+    }
+}
+
 //---------------------------------------------------------------------------
 
 void HqlCppTranslator::doBuildExprAbs(BuildCtx & ctx, IHqlExpression * expr, CHqlBoundExpr & tgt)
@@ -6492,20 +6517,29 @@ void HqlCppTranslator::doBuildExprCast(BuildCtx & ctx, ITypeInfo * to, CHqlBound
                 ensurePushed(ctx, pure);
 
                 bool needToSetPrecision = true;
+                unsigned toDigits = to->getDigits();
+                unsigned toPrecision = to->getPrecision();
                 switch (from->getTypeCode())
                 {
                 case type_int:
                 case type_swapint:
-                    if (to->getDigits() >= from->getDigits())
+                    if (toDigits >= from->getDigits())
                         needToSetPrecision = false;
                     break;
                 case type_decimal:
-                    if (((to->getDigits() - to->getPrecision()) >= (from->getDigits() - from->getPrecision())) &&
-                        (to->getPrecision() >= from->getPrecision()))
-                        needToSetPrecision = false;
-                    break;
+                    {
+                        unsigned fromDigits = from->getDigits();
+                        unsigned fromPrecision = from->getPrecision();
+                        if (((toDigits - toPrecision) >= (fromDigits - fromPrecision)) &&
+                            (toPrecision >= fromPrecision))
+                            needToSetPrecision = false;
+                        break;
+                    }
                 }
 
+                if ((toDigits == MAX_DECIMAL_DIGITS) && (toPrecision == MAX_DECIMAL_PRECISION))
+                    needToSetPrecision = false;
+
                 if (needToSetPrecision)
                 {
                     args.append(*createConstant(createIntValue(to->getDigits(), 1, false)));

+ 1 - 0
ecl/hqlcpp/hqlcpp.ipp

@@ -1488,6 +1488,7 @@ public:
     void doBuildExprSysFunc(BuildCtx & ctx, IHqlExpression * expr, CHqlBoundExpr & tgt, IAtom * funcName);
     void doBuildExprTransfer(BuildCtx & ctx, IHqlExpression * expr, CHqlBoundExpr & tgt);
     void doBuildExprTrim(BuildCtx & ctx, IHqlExpression * target, CHqlBoundExpr & tgt);
+    void doBuildExprTrunc(BuildCtx & ctx, IHqlExpression * target, CHqlBoundExpr & tgt);
     void doBuildExprToFromUnicode(BuildCtx & ctx, IHqlExpression * expr, CHqlBoundExpr & tgt);
     void doBuildExprKeyUnicode(BuildCtx & ctx, IHqlExpression * expr, CHqlBoundExpr & tgt);
     void doBuildExprWuid(BuildCtx & ctx, IHqlExpression * expr, CHqlBoundExpr & tgt);

+ 2 - 2
ecl/hqlcpp/hqlcppsys.ecl

@@ -311,7 +311,6 @@ const char * cppSystemText[]  = {
     "   DecModulus() :  bcd,library='nbcd',entrypoint='DecModulus';",
     "   DecSub() :  bcd,library='nbcd',entrypoint='DecSub';",
     "   DecSubR() : bcd,library='nbcd',entrypoint='DecSubR';",
-    "   DecInt() :  bcd,library='nbcd',entrypoint='DecInt';",
     "   DecNegate() :   bcd,library='nbcd',entrypoint='DecNegate';",
     "   unsigned4 DecPopString(string tgt) :    bcd,library='nbcd',entrypoint='DecPopString';",
     "   DecPopVString(string tgt) : bcd,library='nbcd',entrypoint='DecPopCString';",
@@ -335,10 +334,11 @@ const char * cppSystemText[]  = {
     "   DecDup() :  bcd,library='nbcd',entrypoint='DecDup';",
     "   DecMul() :  bcd,library='nbcd',entrypoint='DecMul';",
     "   DecDivideR() :  bcd,library='nbcd',entrypoint='DecDivideR';",
-    "   DecInfo (out unsigned1 declen, out unsigned1 prec, boolean udec) : bcd,library='nbcd',entrypoint='DecInfo';",
     "   DecLongPower(integer4 pow) :    bcd,library='nbcd',entrypoint='DecLongPower';",
     "   DecPushReal(real8 d) :  bcd,library='nbcd',entrypoint='DecPushReal';",
     "   DecPushUtf8(utf8 d) :   bcd,library='nbcd',entrypoint='rtlDecPushUtf8';",
+    "   DecTruncate() :    bcd,library='nbcd',entrypoint='DecTruncate';",
+    "   DecTruncateAt(unsigned4 places) :  bcd,library='nbcd',entrypoint='DecTruncateAt';",
 
     "   DecSetPrecision(unsigned1 len, unsigned1 prec) : bcd,library='nbcd',entrypoint='DecSetPrecision';",
     "   integer4 DecPopLong() : bcd,library='nbcd',entrypoint='DecPopLong';",

+ 1 - 0
ecl/hqlcpp/hqlhtcpp.cpp

@@ -5051,6 +5051,7 @@ void HqlCppTranslator::buildSetResultInfo(BuildCtx & ctx, IHqlExpression * origi
     OwnedHqlExpr nameText = createResultName(name, isPersist);
     if (retType == type_decimal)
     {
+        assertex(schemaType->getSize() != UNKNOWN_LENGTH);
         //An ugly exception because it takes an arbitrary length decimal.
         //This should be handled by having a decimal(unknown length) parameter to a function which passes size and precision
         CHqlBoundExpr boundName;

+ 56 - 0
ecl/regress/bug85049.ecl

@@ -0,0 +1,56 @@
+/*##############################################################################
+
+    Copyright (C) 2011 HPCC Systems.
+
+    All rights reserved. This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as
+    published by the Free Software Foundation, either version 3 of the
+    License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+############################################################################## */
+output(58d / 100);
+output(58d / 100d);
+output(058d / 100d);
+output(116d / 200d);
+output(nofold(58d) / 100d);
+output(58.00d / 100);
+output(58.00d / 100d);
+output(nofold(58.00d) / 100d);
+
+
+OUTPUT(truncate(58 * 100 / 100), NAMED('Res1'));
+OUTPUT(truncate(58 / 100 * 100), NAMED('Res2'));
+
+OUTPUT(truncate( 58d / 100.0 ), NAMED('TRUNCATE_58d_100_0'));
+OUTPUT(truncate( 58d / 100 ), NAMED('TRUNCATE_58d_100'));
+OUTPUT(truncate( 58d / 100d ), NAMED('TRUNCATE_58d_100d'));
+
+OUTPUT(truncate(658 * 100 / 100), NAMED('Res1b'));
+OUTPUT(truncate(658 / 100 * 100), NAMED('Res2b'));
+
+OUTPUT(truncate( 658d / 100.0 ), NAMED('TRUNCATE_68d_100_0'));
+OUTPUT(truncate( 658d / 100 ), NAMED('TRUNCATE_68d_100'));
+OUTPUT(truncate( 658d / 100d ), NAMED('TRUNCATE_68d_100d'));
+OUTPUT(truncate( 658.00d / 100d ), NAMED('TRUNCATE_68d_100dy'));
+
+OUTPUT(truncate(nofold(58) * 100 / 100), NAMED('Res1cx'));
+OUTPUT(truncate(nofold(58) / 100 * 100), NAMED('Res2cx'));
+
+OUTPUT(truncate( nofold(58d) / 100.0 ), NAMED('TRUNCATE_58d_100_0x'));
+OUTPUT(truncate( nofold(58d) / 100 ), NAMED('TRUNCATE_58d_100x'));
+OUTPUT(truncate( nofold(58d) / 100d ), NAMED('TRUNCATE_58d_100dx'));
+
+OUTPUT(truncate(nofold(658) * 100 / 100), NAMED('Res1dx'));
+OUTPUT(truncate(nofold(658) / 100 * 100), NAMED('Res2dx'));
+
+
+OUTPUT(truncate( nofold(658d) / 100.0 ), NAMED('TRUNCATE_68d_100_0x'));
+OUTPUT(truncate( nofold(658d) / 100 ), NAMED('TRUNCATE_68d_100x'));
+OUTPUT(truncate( nofold(658d) / 100d ), NAMED('TRUNCATE_68d_100dx'));

+ 17 - 0
ecl/regress/round.ecl

@@ -1,3 +1,20 @@
+/*##############################################################################
+
+    Copyright (C) 2011 HPCC Systems.
+
+    All rights reserved. This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as
+    published by the Free Software Foundation, either version 3 of the
+    License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+############################################################################## */
 LOADXML('<xml/>');
 
 show(value) := macro

+ 17 - 0
ecl/regress/round2.ecl

@@ -1,3 +1,20 @@
+/*##############################################################################
+
+    Copyright (C) 2011 HPCC Systems.
+
+    All rights reserved. This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as
+    published by the Free Software Foundation, either version 3 of the
+    License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+############################################################################## */
 LOADXML('<xml/>');
 
 show(value) := macro

+ 17 - 0
ecl/regress/round3.ecl

@@ -1,3 +1,20 @@
+/*##############################################################################
+
+    Copyright (C) 2011 HPCC Systems.
+
+    All rights reserved. This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as
+    published by the Free Software Foundation, either version 3 of the
+    License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+############################################################################## */
 //Make sure both of these don't lose the extra digit.
 output((string)(round(9.9D)) + '\n');
 output((string)(round(5D, -1)) + '\n');

+ 4 - 2
rtl/nbcd/bcd.hpp

@@ -46,8 +46,8 @@ nbcd_decl void  _fastcall  DecDup( void );         // duplicate value on top of
 nbcd_decl void  _fastcall  DecSetPrecision(unsigned char declen, unsigned char prec); // changes length and precision of top value on stack
 nbcd_decl void  _fastcall  DecSub( void );         // substract top from second value on stack and replace with result
 nbcd_decl void  _fastcall  DecSubR( void );        // substract second from top value on stack and replace with result
-nbcd_decl void  _fastcall  DecInfo (unsigned char & declen, unsigned char & prec, bool udec=false); // returns the decimal (or udecimal) length and precision of top value on stack
-nbcd_decl void  _fastcall  DecInt( void );         // turn value on top of the decimal stack into integral number
+nbcd_decl void  _fastcall  DecInfo(unsigned & digits, unsigned & prec); // returns the digits and precision of top value on stack
+nbcd_decl void  _fastcall  DecClipInfo(unsigned & digits, unsigned & prec);
 nbcd_decl void  _fastcall  DecLongPower(long pow);   // calculates top of stack to the power of long and replaces with result
 nbcd_decl void  _fastcall  DecModulus( void );   // modulus second by top value on stack and replace with result
 nbcd_decl void  _fastcall  DecMul( void );         // multiply values top and second on the stack and replace with result
@@ -77,6 +77,8 @@ nbcd_decl void  _fastcall  DecRoundUp( void );       // round value on top of de
 nbcd_decl void  _fastcall  DecRoundTo( unsigned places );       // round value on top of decimal stack
 nbcd_decl void  * _fastcall  DecSaveStack( void );   // Save decimal stack
 nbcd_decl void  _fastcall  DecSwap( void );          // swap top and second values on decimal stack 
+nbcd_decl void  _fastcall  DecTruncate( void );       // truncate value on top of decimal stack
+nbcd_decl void  _fastcall  DecTruncateAt(unsigned places);       // truncate value on top of decimal stack
 nbcd_decl void  _fastcall  DecUlongPower(unsigned long pow); // calculates top of stack to the power of unsigned long and replaces with result
 
 nbcd_decl void  _fastcall  DecLock();

+ 42 - 5
rtl/nbcd/nbcd.cpp

@@ -226,6 +226,7 @@ TempDecimal & TempDecimal::divide(const TempDecimal & other)
         if (iter < maxDigits)
             digits[iter] = q;
     }
+    //MORE: This should really calculate the next digit, and conditionally round the least significant digit.
 
     negative ^= other.negative;
     return *this;
@@ -253,7 +254,7 @@ void TempDecimal::extendRange(byte oLsb, byte oMsb)
 TempDecimal & TempDecimal::modulus(const TempDecimal & other)
 {
     TempDecimal left(*this);
-    left.divide(other).trunc(0).multiply(other);
+    left.divide(other).truncate(0).multiply(other);
     return subtract(left);
 }
 
@@ -418,6 +419,35 @@ TempDecimal & TempDecimal::roundup(int places)
 }
 
 
+void TempDecimal::getPrecision(unsigned & digits, unsigned & precision)
+{
+    //Ensures digits>=precision && precision >= 0
+    unsigned top = msb >= zeroDigit ? msb+1 : zeroDigit;
+    unsigned low = lsb >= zeroDigit ? zeroDigit : lsb;
+    digits = (top == low) ? 1 : top - low;
+    precision = zeroDigit-low;
+}
+
+void TempDecimal::getClipPrecision(unsigned & digits, unsigned & precision)
+{
+    int lo, hi;
+    clip(lo, hi);
+
+    if (lo > hi)
+    {
+        digits = 1;
+        precision = 0;
+    }
+    else
+    {
+        //Ensures digits>=precision && precision >= 0
+        unsigned top = hi >= zeroDigit ? hi+1 : zeroDigit;
+        unsigned low = lo >= zeroDigit ? zeroDigit : lo;
+        digits = (top == low) ? 1 : top - low;
+        precision = zeroDigit-low;
+    }
+}
+
 TempDecimal & TempDecimal::setPrecision(byte numDigits, byte precision)
 {
     unsigned char newhigh = zeroDigit + numDigits - precision - 1;
@@ -497,21 +527,28 @@ TempDecimal & TempDecimal::subtractDigits(const TempDecimal & other)
     return *this;
 }
 
-TempDecimal & TempDecimal::trunc(unsigned places)
+TempDecimal & TempDecimal::truncate(int places)
 {
-    if ((places < maxPrecision) && (zeroDigit - places > lsb))
+    //out of range - either 0 or overflow
+    if (places <= -maxIntegerDigits)
+    {
+        setZero();
+        return *this;
+    }
+
+    if (zeroDigit - places > lsb)
     {
         lsb = zeroDigit - places;
-        //very rare - e.g., trunc (0.0001)
         if (lsb > msb)
         {
-            msb = lsb;
             digits[lsb] = 0;
+            msb = lsb;
         }
     }
     return *this;
 }
 
+
 size32_t TempDecimal::getStringLength() const
 {
     int lo, hi;

+ 8 - 5
rtl/nbcd/nbcd.hpp

@@ -51,7 +51,7 @@ public:
     TempDecimal & roundup(int places=0);        // -ve means left of decimal point e.g., -3 = to nearest 1000.
     TempDecimal & setPrecision(byte numDigits, byte precision);
     TempDecimal & subtract(const TempDecimal & other);
-    TempDecimal & trunc(unsigned places=0);
+    TempDecimal & truncate(int places=0);
 
     size32_t getStringLength() const;
     void getCString(size32_t length, char * buffer) const;
@@ -66,6 +66,9 @@ public:
     unsigned __int64 getUInt64() const;
     unsigned int getUInt() const;
 
+    void getClipPrecision(unsigned & digits, unsigned & precision);
+    void getPrecision(unsigned & digits, unsigned & precison);
+
     void set(const TempDecimal & value);
     void setCString(const char * buffer);
     void setDecimal(byte length, byte precision, const void * buffer);
@@ -114,16 +117,16 @@ private:
 
 protected:
     enum { 
-        maxDigits=64, 
-        maxPrecision=32, 
-        maxIntegerDigits=(maxDigits-maxPrecision),
+        maxDigits=MAX_DECIMAL_DIGITS,
+        maxPrecision=MAX_DECIMAL_PRECISION,
+        maxIntegerDigits=MAX_DECIMAL_LEADING,
         lastDigit = maxDigits-1, 
         zeroDigit = (maxDigits-maxIntegerDigits), 
     };
     byte digits[maxDigits];                 // stored little endian.
     byte msb;
     byte lsb;
-    byte negative;
+    byte negative;                          // byte to allow ^ operation
 };
 
 

+ 14 - 4
rtl/nbcd/nbcds.cpp

@@ -103,14 +103,14 @@ nbcd_decl void _fastcall  DecSubR()
     DecSub();
 }
 
-nbcd_decl void _fastcall  DecInfo (unsigned char & declen, unsigned char & prec, bool udec)
+nbcd_decl void _fastcall  DecInfo (unsigned & digits, unsigned & prec)
 {
-    UNIMPLEMENTED;
+    stack[curStack-1].getPrecision(digits, prec);
 }
 
-nbcd_decl void _fastcall  DecInt()
+nbcd_decl void _fastcall  DecClipInfo (unsigned & digits, unsigned & prec)
 {
-    stack[curStack-1].trunc(0);
+    stack[curStack-1].getClipPrecision(digits, prec);
 }
 
 nbcd_decl void _fastcall  DecLongPower(long pow)
@@ -260,6 +260,16 @@ nbcd_decl void _fastcall  DecSwap()
 }
 
 
+nbcd_decl void _fastcall  DecTruncate()
+{
+    stack[curStack-1].truncate(0);
+}
+
+nbcd_decl void _fastcall  DecTruncateAt(unsigned places)
+{
+    stack[curStack-1].truncate(places);
+}
+
 nbcd_decl bool _fastcall  DecValid(bool isSigned, unsigned digits, const void * data)
 {
     return decValid(isSigned, digits, data);

+ 4 - 4
rtl/nbcd/nbcdtest.cpp

@@ -202,13 +202,13 @@ protected:
         DBGLOG("1234567.89012346 = %.8f (real)", c.getReal());
 
         c = "9.53456";
-        c.trunc(4);
+        c.truncate(4);
         checkDecimal(c, "9.5345");
-        c.trunc(8);
+        c.truncate(8);
         checkDecimal(c, "9.5345");
-        c.trunc(2);
+        c.truncate(2);
         checkDecimal(c, "9.53");
-        c.trunc();
+        c.truncate();
         checkDecimal(c, "9");
 
         a = "123.2345";

+ 4 - 0
system/include/platform.h

@@ -427,6 +427,10 @@ typedef int socklen_t;
 #define FLOAT_SIG_DIGITS    7
 #define DOUBLE_SIG_DIGITS   16
 
+#define MAX_DECIMAL_LEADING    32          // Maximum number of leading digits in a decimal field
+#define MAX_DECIMAL_PRECISION  32          // Maximum digits in a decimal field
+#define MAX_DECIMAL_DIGITS     (MAX_DECIMAL_LEADING+MAX_DECIMAL_PRECISION)
+
 #define strtok(a,b)   j_strtok_deprecated(a,b)  // will disappear at some point
 
 typedef unsigned __int64 __uint64;

+ 37 - 0
testing/ecl/bcd1.ecl

@@ -0,0 +1,37 @@
+/*##############################################################################
+
+    Copyright (C) 2011 HPCC Systems.
+
+    All rights reserved. This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as
+    published by the Free Software Foundation, either version 3 of the
+    License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+############################################################################## */
+
+// this behaves the way I want, but can't I use BCD rather than float?
+udecimal8_2 pct_real(string n, string d) := (real)n / (real)d * 100.0;
+output( pct_real('1234','10000') );
+
+// the rest of these all evaluate to zero... not sure why
+udecimal8_2 pct_bcd(string n, string d) := (udecimal8_4)n / (udecimal9_4)d * 100.0;
+output( pct_bcd('1234','10000') );
+
+output( (udecimal8_4)0.1234 * 100 );
+output( (udecimal8_4)0.1234 * 100.0 );
+output( (udecimal8_4)0.1234 * (udecimal8_4)100 );
+output( (udecimal8_4)0.1234 * (udecimal8_4)100.0 );
+
+output( pct_bcd(nofold('1234'),nofold('10000')) );
+
+output( (udecimal8_4)nofold(0.1234) * 100 );
+output( (udecimal8_4)nofold(0.1234) * 100.0 );
+output( (udecimal8_4)nofold(0.1234) * (udecimal8_4)nofold(100) );
+output( (udecimal8_4)nofold(0.1234) * (udecimal8_4)nofold(100.0) );

+ 42 - 0
testing/ecl/bcd2.ecl

@@ -0,0 +1,42 @@
+/*##############################################################################
+
+    Copyright (C) 2011 HPCC Systems.
+
+    All rights reserved. This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as
+    published by the Free Software Foundation, either version 3 of the
+    License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+############################################################################## */
+
+
+
+decimal17_4 sum1 := 20;
+unsigned count1 := 3;
+output((decimal20_10)(sum1/count1));
+
+
+decimal17_4 sum2 := 20 : stored('sum2');
+unsigned count2 := 3 : stored('count2');
+output((decimal20_10)(sum2/count2));
+
+rec := { decimal17_4 sumx, unsigned countx };
+ds := dataset([{20,3},{10,2},{10.0001,2}], rec);
+output(nofold(ds), { sumx, countx, decimal20_10 average := sumx/countx, sumx between 10 and 10.00009, sumx between 10D and 10.00009D });
+
+
+decimal17_4 value1 := 1.6667;
+decimal17_4 value2 := 1.6667 : stored('value2');
+
+output(round(value1));
+output(roundup((real)value1));
+
+output(round((real)value2));
+output(roundup((real)value2));

+ 56 - 0
testing/ecl/bcd4.ecl

@@ -0,0 +1,56 @@
+/*##############################################################################
+
+    Copyright (C) 2011 HPCC Systems.
+
+    All rights reserved. This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as
+    published by the Free Software Foundation, either version 3 of the
+    License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+############################################################################## */
+output(58d / 100);
+output(58d / 100d);
+output(058d / 100d);
+output(116d / 200d);
+output(nofold(58d) / 100d);
+output(58.00d / 100);
+output(58.00d / 100d);
+output(nofold(58.00d) / 100d);
+
+
+OUTPUT(truncate(58 * 100 / 100), NAMED('Res1'));
+OUTPUT(truncate(58 / 100 * 100), NAMED('Res2'));
+
+OUTPUT(truncate( 58d / 100.0 ), NAMED('TRUNCATE_58d_100_0'));
+OUTPUT(truncate( 58d / 100 ), NAMED('TRUNCATE_58d_100'));
+OUTPUT(truncate( 58d / 100d ), NAMED('TRUNCATE_58d_100d'));
+
+OUTPUT(truncate(658 * 100 / 100), NAMED('Res1b'));
+OUTPUT(truncate(658 / 100 * 100), NAMED('Res2b'));
+
+OUTPUT(truncate( 658d / 100.0 ), NAMED('TRUNCATE_68d_100_0'));
+OUTPUT(truncate( 658d / 100 ), NAMED('TRUNCATE_68d_100'));
+OUTPUT(truncate( 658d / 100d ), NAMED('TRUNCATE_68d_100d'));
+OUTPUT(truncate( 658.00d / 100d ), NAMED('TRUNCATE_68d_100dy'));
+
+OUTPUT(truncate(nofold(58) * 100 / 100), NAMED('Res1cx'));
+OUTPUT(truncate(nofold(58) / 100 * 100), NAMED('Res2cx'));
+
+OUTPUT(truncate( nofold(58d) / 100.0 ), NAMED('TRUNCATE_58d_100_0x'));
+OUTPUT(truncate( nofold(58d) / 100 ), NAMED('TRUNCATE_58d_100x'));
+OUTPUT(truncate( nofold(58d) / 100d ), NAMED('TRUNCATE_58d_100dx'));
+
+OUTPUT(truncate(nofold(658) * 100 / 100), NAMED('Res1dx'));
+OUTPUT(truncate(nofold(658) / 100 * 100), NAMED('Res2dx'));
+
+
+OUTPUT(truncate( nofold(658d) / 100.0 ), NAMED('TRUNCATE_68d_100_0x'));
+OUTPUT(truncate( nofold(658d) / 100 ), NAMED('TRUNCATE_68d_100x'));
+OUTPUT(truncate( nofold(658d) / 100d ), NAMED('TRUNCATE_68d_100dx'));

+ 33 - 0
testing/ecl/key/bcd1.xml

@@ -0,0 +1,33 @@
+<Dataset name='Result 1'>
+ <Row><Result_1>12.34</Result_1></Row>
+</Dataset>
+<Dataset name='Result 2'>
+ <Row><Result_2>12.34</Result_2></Row>
+</Dataset>
+<Dataset name='Result 3'>
+ <Row><Result_3>12.34</Result_3></Row>
+</Dataset>
+<Dataset name='Result 4'>
+ <Row><Result_4>12.34</Result_4></Row>
+</Dataset>
+<Dataset name='Result 5'>
+ <Row><Result_5>12.34</Result_5></Row>
+</Dataset>
+<Dataset name='Result 6'>
+ <Row><Result_6>12.34</Result_6></Row>
+</Dataset>
+<Dataset name='Result 7'>
+ <Row><Result_7>12.34</Result_7></Row>
+</Dataset>
+<Dataset name='Result 8'>
+ <Row><Result_8>12.34</Result_8></Row>
+</Dataset>
+<Dataset name='Result 9'>
+ <Row><Result_9>12.34</Result_9></Row>
+</Dataset>
+<Dataset name='Result 10'>
+ <Row><Result_10>12.34</Result_10></Row>
+</Dataset>
+<Dataset name='Result 11'>
+ <Row><Result_11>12.34</Result_11></Row>
+</Dataset>

+ 23 - 0
testing/ecl/key/bcd2.xml

@@ -0,0 +1,23 @@
+<Dataset name='Result 1'>
+ <Row><Result_1>6.6666666667</Result_1></Row>
+</Dataset>
+<Dataset name='Result 2'>
+ <Row><Result_2>6.6666666667</Result_2></Row>
+</Dataset>
+<Dataset name='Result 3'>
+ <Row><sumx>20</sumx><countx>3</countx><average>6.6666666667</average><_unnamed_4>false</_unnamed_4><_unnamed_5>false</_unnamed_5></Row>
+ <Row><sumx>10</sumx><countx>2</countx><average>5</average><_unnamed_4>true</_unnamed_4><_unnamed_5>true</_unnamed_5></Row>
+ <Row><sumx>10.0001</sumx><countx>2</countx><average>5.00005</average><_unnamed_4>false</_unnamed_4><_unnamed_5>false</_unnamed_5></Row>
+</Dataset>
+<Dataset name='Result 4'>
+ <Row><Result_4>2</Result_4></Row>
+</Dataset>
+<Dataset name='Result 5'>
+ <Row><Result_5>2</Result_5></Row>
+</Dataset>
+<Dataset name='Result 6'>
+ <Row><Result_6>2</Result_6></Row>
+</Dataset>
+<Dataset name='Result 7'>
+ <Row><Result_7>2</Result_7></Row>
+</Dataset>

+ 87 - 0
testing/ecl/key/bcd4.xml

@@ -0,0 +1,87 @@
+<Dataset name='Result 1'>
+ <Row><Result_1>0.58</Result_1></Row>
+</Dataset>
+<Dataset name='Result 2'>
+ <Row><Result_2>0.58</Result_2></Row>
+</Dataset>
+<Dataset name='Result 3'>
+ <Row><Result_3>0.58</Result_3></Row>
+</Dataset>
+<Dataset name='Result 4'>
+ <Row><Result_4>0.58</Result_4></Row>
+</Dataset>
+<Dataset name='Result 5'>
+ <Row><Result_5>0.58</Result_5></Row>
+</Dataset>
+<Dataset name='Result 6'>
+ <Row><Result_6>0.58</Result_6></Row>
+</Dataset>
+<Dataset name='Result 7'>
+ <Row><Result_7>0.58</Result_7></Row>
+</Dataset>
+<Dataset name='Result 8'>
+ <Row><Result_8>0.58</Result_8></Row>
+</Dataset>
+<Dataset name='Res1'>
+ <Row><Res1>58</Res1></Row>
+</Dataset>
+<Dataset name='Res2'>
+ <Row><Res2>57</Res2></Row>
+</Dataset>
+<Dataset name='TRUNCATE_58d_100_0'>
+ <Row><TRUNCATE_58d_100_0>0</TRUNCATE_58d_100_0></Row>
+</Dataset>
+<Dataset name='TRUNCATE_58d_100'>
+ <Row><TRUNCATE_58d_100>0</TRUNCATE_58d_100></Row>
+</Dataset>
+<Dataset name='TRUNCATE_58d_100d'>
+ <Row><TRUNCATE_58d_100d>0</TRUNCATE_58d_100d></Row>
+</Dataset>
+<Dataset name='Res1b'>
+ <Row><Res1b>658</Res1b></Row>
+</Dataset>
+<Dataset name='Res2b'>
+ <Row><Res2b>658</Res2b></Row>
+</Dataset>
+<Dataset name='TRUNCATE_68d_100_0'>
+ <Row><TRUNCATE_68d_100_0>6</TRUNCATE_68d_100_0></Row>
+</Dataset>
+<Dataset name='TRUNCATE_68d_100'>
+ <Row><TRUNCATE_68d_100>6</TRUNCATE_68d_100></Row>
+</Dataset>
+<Dataset name='TRUNCATE_68d_100d'>
+ <Row><TRUNCATE_68d_100d>6</TRUNCATE_68d_100d></Row>
+</Dataset>
+<Dataset name='TRUNCATE_68d_100dy'>
+ <Row><TRUNCATE_68d_100dy>6</TRUNCATE_68d_100dy></Row>
+</Dataset>
+<Dataset name='Res1cx'>
+ <Row><Res1cx>58</Res1cx></Row>
+</Dataset>
+<Dataset name='Res2cx'>
+ <Row><Res2cx>57</Res2cx></Row>
+</Dataset>
+<Dataset name='TRUNCATE_58d_100_0x'>
+ <Row><TRUNCATE_58d_100_0x>0</TRUNCATE_58d_100_0x></Row>
+</Dataset>
+<Dataset name='TRUNCATE_58d_100x'>
+ <Row><TRUNCATE_58d_100x>0</TRUNCATE_58d_100x></Row>
+</Dataset>
+<Dataset name='TRUNCATE_58d_100dx'>
+ <Row><TRUNCATE_58d_100dx>0</TRUNCATE_58d_100dx></Row>
+</Dataset>
+<Dataset name='Res1dx'>
+ <Row><Res1dx>658</Res1dx></Row>
+</Dataset>
+<Dataset name='Res2dx'>
+ <Row><Res2dx>658</Res2dx></Row>
+</Dataset>
+<Dataset name='TRUNCATE_68d_100_0x'>
+ <Row><TRUNCATE_68d_100_0x>6</TRUNCATE_68d_100_0x></Row>
+</Dataset>
+<Dataset name='TRUNCATE_68d_100x'>
+ <Row><TRUNCATE_68d_100x>6</TRUNCATE_68d_100x></Row>
+</Dataset>
+<Dataset name='TRUNCATE_68d_100dx'>
+ <Row><TRUNCATE_68d_100dx>6</TRUNCATE_68d_100dx></Row>
+</Dataset>

Plik diff jest za duży
+ 1888 - 0
testing/ecl/key/round.xml


Plik diff jest za duży
+ 1888 - 0
testing/ecl/key/round2.xml


+ 28 - 0
testing/ecl/key/round3.xml

@@ -0,0 +1,28 @@
+<Dataset name='Result 1'>
+ <Row><Result_1>10
+</Result_1></Row>
+</Dataset>
+<Dataset name='Result 2'>
+ <Row><Result_2>10
+</Result_2></Row>
+</Dataset>
+<Dataset name='Result 3'>
+ <Row><Result_3>1
+</Result_3></Row>
+</Dataset>
+<Dataset name='Result 4'>
+ <Row><Result_4>1.1
+</Result_4></Row>
+</Dataset>
+<Dataset name='Result 5'>
+ <Row><Result_5>0
+</Result_5></Row>
+</Dataset>
+<Dataset name='Result 6'>
+ <Row><Result_6>1234600.0
+</Result_6></Row>
+</Dataset>
+<Dataset name='Result 7'>
+ <Row><Result_7>1234600.0
+</Result_7></Row>
+</Dataset>

+ 85 - 0
testing/ecl/round.ecl

@@ -0,0 +1,85 @@
+/*##############################################################################
+
+    Copyright (C) 2011 HPCC Systems.
+
+    All rights reserved. This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as
+    published by the Free Software Foundation, either version 3 of the
+    License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+############################################################################## */
+
+LOADXML('<xml/>');
+
+show(value) := macro
+    output((string) value + ' =\n');
+    output('   round     = ' + (string)round(value) + '\n');
+    output('   round(0)  = ' + (string)round(value, 0) + '\n');
+    output('   round(1)  = ' + (string)round(value, 1) + '\n');
+    output('   round(10) = ' + (string)round(value, 10) + '\n');
+    output('   round(32) = ' + (string)round(value, 32) + '\n');
+    output('   round(100) = ' + (string)round(value, 100) + '\n');
+    output('   round(-1) = ' + (string)round(value, -1) + '\n');
+    output('   round(-10) = ' + (string)round(value, -10) + '\n');
+    output('   round(-100)= ' + (string)round(value, -100) + '\n');
+    output('----\n');
+endmacro;
+
+show(0.0D);
+show(1.0D);
+show(2.1D);
+show(2.4999D);
+show(2.5000D);
+show(2.05000D);
+show(5D);
+show(1230000D);
+show(0.100000D);
+show(1.99999999995D);
+show(1234567890123456789.0D);
+show(0.1234567890123456789D);
+show(0.00000000000000000000123456789018);
+show(-1234567.890123456789D);
+show(12345678901234567890000000000000D);
+
+show(0.0);
+show(1.0);
+show(2.1);
+show(2.4999);
+show(2.5000);
+show(2.05000);
+show(5.0);
+show(1230000.0);
+show(0.100000);
+show(1.99999999995);
+show(1234567890123456789.0);
+show(0.1234567890123456789);
+show(0.000000000000000000001234567890123456789);
+show(-1234567.890123456789);
+show(1234567890123456789000000000000000.0);
+show(1.0e100);
+show(1.0e-100);
+show(1230000);
+
+showRounded(value, minPos, maxPos) := MACRO
+#SET(i,minPos)
+#LOOP
+  #IF (%i%>maxpos)
+    #BREAK
+  #END
+  output((string)%i% + ' -> ' + (string)round(value,%i%) + '\n');
+  #SET(i,%i%+1)
+#END
+ENDMACRO;
+
+
+#DECLARE(i)
+showRounded(123456789.0, -10, 0);
+showRounded(1234567890123456789.0, -28, -1);
+showRounded(1234567890123456789.0e50, -70, -1);

+ 85 - 0
testing/ecl/round2.ecl

@@ -0,0 +1,85 @@
+/*##############################################################################
+
+    Copyright (C) 2011 HPCC Systems.
+
+    All rights reserved. This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as
+    published by the Free Software Foundation, either version 3 of the
+    License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+############################################################################## */
+
+LOADXML('<xml/>');
+
+show(value) := macro
+    output((string) value + ' =\n');
+    output('   round     = ' + (string)round(value) + '\n');
+    output('   round(0)  = ' + (string)round(value, 0) + '\n');
+    output('   round(1)  = ' + (string)round(value, 1) + '\n');
+    output('   round(10) = ' + (string)round(value, 10) + '\n');
+    output('   round(32) = ' + (string)round(value, 32) + '\n');
+    output('   round(100) = ' + (string)round(value, 100) + '\n');
+    output('   round(-1) = ' + (string)round(value, -1) + '\n');
+    output('   round(-10) = ' + (string)round(value, -10) + '\n');
+    output('   round(-100)= ' + (string)round(value, -100) + '\n');
+    output('----\n');
+endmacro;
+
+show(nofold(0.0D));
+show(nofold(1.0D));
+show(nofold(2.1D));
+show(nofold(2.4999D));
+show(nofold(2.5000D));
+show(nofold(2.05000D));
+show(nofold(5D));
+show(nofold(1230000D));
+show(nofold(0.100000D));
+show(nofold(1.99999999995D));
+show(nofold(1234567890123456789.0D));
+show(nofold(0.1234567890123456789D));
+show(nofold(0.00000000000000000000123456789012));
+show(-nofold(1234567.890123456789D));
+show(nofold(12345678901234567890000000000000D));
+
+show(nofold(0.0));
+show(nofold(1.0));
+show(nofold(2.1));
+show(nofold(2.4999));
+show(nofold(2.5000));
+show(nofold(2.05000));
+show(nofold(5.0));
+show(nofold(1230000.0));
+show(nofold(0.100000));
+show(nofold(1.99999999995));
+show(nofold(1234567890123456789.0));
+show(nofold(0.1234567890123456789));
+show(nofold(0.000000000000000000001234567890123456789));
+show(-nofold(1234567.890123456789));
+show(nofold(1234567890123456789000000000000000.0));
+show(nofold(1.0e100));
+show(nofold(1.0e-100));
+show(nofold(1230000));
+
+showRounded(value, minPos, maxPos) := MACRO
+#SET(i,minPos)
+#LOOP
+  #IF (%i%>maxpos)
+    #BREAK
+  #END
+  output((string)%i% + ' -> ' + (string)round(nofold(value),%i%) + '\n');
+  #SET(i,%i%+1)
+#END
+ENDMACRO;
+
+
+#DECLARE(i)
+showRounded(123456789.0, -10, 0);
+showRounded(1234567890123456789.0, -28, -1);
+showRounded(1234567890123456789.0e50, -70, -1);

+ 30 - 0
testing/ecl/round3.ecl

@@ -0,0 +1,30 @@
+/*##############################################################################
+
+    Copyright (C) 2011 HPCC Systems.
+
+    All rights reserved. This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as
+    published by the Free Software Foundation, either version 3 of the
+    License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+############################################################################## */
+
+//Make sure both of these don't lose the extra digit.
+output((string)(round(9.9D)) + '\n');
+output((string)(round(5D, -1)) + '\n');
+
+
+output((string)(round(nofold(1.1D), 0)) + '\n');
+output((string)(round(nofold(1.1D), 1)) + '\n');
+output((string)(round(nofold(1.1D), -1)) + '\n');
+
+
+output((string)(round(1234567, -2)) + '\n');
+output((string)(round(nofold(1234567), -2)) + '\n');