1
0
mirror of https://github.com/mariadb-corporation/mariadb-columnstore-engine.git synced 2025-08-08 14:22:09 +03:00

MCOL-1822 add LONG DOUBLE support

This commit is contained in:
David Hall
2019-01-29 09:55:43 -06:00
parent ee2cb7b0de
commit c5b9ae11e5
40 changed files with 746 additions and 38 deletions

View File

@@ -498,6 +498,14 @@ void AggregateColumn::evaluate(Row& row, bool& isNull)
break;
case CalpontSystemCatalog::LONGDOUBLE:
if (row.equals(LONGDOUBLENULL, fInputIndex))
isNull = true;
else
fResult.longDoubleVal = row.getLongDoubleField(fInputIndex);
break;
case CalpontSystemCatalog::DECIMAL:
case CalpontSystemCatalog::UDECIMAL:
switch (fResultType.colWidth)

View File

@@ -155,6 +155,10 @@ const string colDataTypeToString(CalpontSystemCatalog::ColDataType cdt)
return "double";
break;
case CalpontSystemCatalog::LONGDOUBLE:
return "long double";
break;
case CalpontSystemCatalog::DATETIME:
return "datetime";
break;

View File

@@ -76,6 +76,8 @@ const float MAX_FLOAT __attribute__ ((unused)) = std::numeric_limits<float>::ma
const float MIN_FLOAT __attribute__ ((unused)) = -std::numeric_limits<float>::max();
const double MAX_DOUBLE __attribute__ ((unused)) = std::numeric_limits<double>::max(); //1.7976931348623157e+308
const double MIN_DOUBLE __attribute__ ((unused)) = -std::numeric_limits<double>::max();
const long double MAX_LONGDOUBLE __attribute__ ((unused)) = std::numeric_limits<long double>::max(); //1.7976931348623157e+308
const long double MIN_LONGDOUBLE __attribute__ ((unused)) = -std::numeric_limits<long double>::max();
const uint64_t AUTOINCR_SATURATED __attribute__ ((unused)) = std::numeric_limits<uint64_t>::max();

View File

@@ -51,7 +51,7 @@ ConstantColumn::ConstantColumn(const string& sql, TYPE type) :
fData(sql)
{
fResult.strVal = sql;
if (type == LITERAL && sql.length() < 9)
{
memcpy(tmp, sql.c_str(), sql.length());
@@ -67,6 +67,7 @@ ConstantColumn::ConstantColumn(const string& sql, TYPE type) :
fResult.floatVal = atof(sql.c_str());
fResult.doubleVal = atof(sql.c_str());
fResult.longDoubleVal = strtold(sql.c_str(), NULL);
// decimal for constant should be constructed by the caller and call the decimal constructor
fResult.decimalVal.value = fResult.intVal;
@@ -105,6 +106,27 @@ ConstantColumn::ConstantColumn(const string& sql, const double val) :
fResult.intVal = (int64_t)val;
fResult.uintVal = (uint64_t)val;
fResult.floatVal = (float)val;
fResult.longDoubleVal = val;
// decimal for constant should be constructed by the caller and call the decimal constructor
fResult.decimalVal.value = fResult.intVal;
fResult.decimalVal.scale = 0;
fResult.decimalVal.precision = 18;
fResultType.colDataType = CalpontSystemCatalog::DOUBLE;
fResultType.colWidth = 8;
}
ConstantColumn::ConstantColumn(const string& sql, const long double val) :
ReturnedColumn(),
fConstval(sql),
fType(NUM),
fData(sql)
{
fResult.strVal = sql;
fResult.doubleVal = (double)val;
fResult.intVal = (int64_t)val;
fResult.uintVal = (uint64_t)val;
fResult.floatVal = (float)val;
fResult.longDoubleVal = val;
// decimal for constant should be constructed by the caller and call the decimal constructor
fResult.decimalVal.value = fResult.intVal;
fResult.decimalVal.scale = 0;
@@ -124,6 +146,7 @@ ConstantColumn::ConstantColumn(const string& sql, const int64_t val, TYPE type)
fResult.uintVal = (uint64_t)fResult.intVal;
fResult.floatVal = (float)fResult.intVal;
fResult.doubleVal = (double)fResult.intVal;
fResult.longDoubleVal = (long double)fResult.intVal;
fResult.decimalVal.value = fResult.intVal;
fResult.decimalVal.scale = 0;
fResultType.colDataType = CalpontSystemCatalog::BIGINT;
@@ -141,6 +164,7 @@ ConstantColumn::ConstantColumn(const string& sql, const uint64_t val, TYPE type)
fResult.intVal = (int64_t)fResult.uintVal;
fResult.floatVal = (float)fResult.uintVal;
fResult.doubleVal = (double)fResult.uintVal;
fResult.longDoubleVal = (long double)fResult.uintVal;
fResult.decimalVal.value = fResult.uintVal;
fResult.decimalVal.scale = 0;
fResultType.colDataType = CalpontSystemCatalog::UBIGINT;
@@ -158,6 +182,7 @@ ConstantColumn::ConstantColumn(const string& sql, const IDB_Decimal& val) :
fResult.uintVal = strtoull(sql.c_str(), NULL, 0);
fResult.floatVal = atof(sql.c_str());
fResult.doubleVal = atof(sql.c_str());
fResult.longDoubleVal = strtold(sql.c_str(), NULL);
fResult.decimalVal = val;
fResultType.colDataType = CalpontSystemCatalog::DECIMAL;
fResultType.colWidth = 8;
@@ -201,6 +226,7 @@ ConstantColumn::ConstantColumn(const int64_t val, TYPE type) :
fResult.uintVal = (uint64_t)fResult.intVal;
fResult.floatVal = (float)fResult.intVal;
fResult.doubleVal = (double)fResult.intVal;
fResult.longDoubleVal = (long double)fResult.intVal;
fResult.decimalVal.value = fResult.intVal;
fResult.decimalVal.scale = 0;
fResultType.colDataType = CalpontSystemCatalog::BIGINT;
@@ -220,6 +246,7 @@ ConstantColumn::ConstantColumn(const uint64_t val, TYPE type) :
fResult.uintVal = val;
fResult.floatVal = (float)fResult.uintVal;
fResult.doubleVal = (double)fResult.uintVal;
fResult.longDoubleVal = (long double)fResult.uintVal;
fResult.decimalVal.value = fResult.uintVal;
fResult.decimalVal.scale = 0;
fResultType.colDataType = CalpontSystemCatalog::UBIGINT;
@@ -281,13 +308,15 @@ void ConstantColumn::serialize(messageqcpp::ByteStream& b) const
b << static_cast<const ByteStream::doublebyte>(fReturnAll);
b << (uint64_t)fResult.intVal;
b << fResult.uintVal;
b << (*(uint64_t*)(&fResult.doubleVal));
b << (*(uint32_t*)(&fResult.floatVal));
b << fResult.doubleVal;
b << fResult.longDoubleVal;
b << fResult.floatVal;
b << (uint8_t)fResult.boolVal;
b << fResult.strVal;
b << (uint64_t)fResult.decimalVal.value;
b << (uint8_t)fResult.decimalVal.scale;
b << (uint8_t)fResult.decimalVal.precision;
b << fResult.longDoubleVal;
}
void ConstantColumn::unserialize(messageqcpp::ByteStream& b)
@@ -303,8 +332,9 @@ void ConstantColumn::unserialize(messageqcpp::ByteStream& b)
b >> reinterpret_cast< ByteStream::doublebyte&>(fReturnAll);
b >> (uint64_t&)fResult.intVal;
b >> fResult.uintVal;
b >> (uint64_t&)fResult.doubleVal;
b >> (uint32_t&)fResult.floatVal;
b >> fResult.doubleVal;
b >> fResult.longDoubleVal;
b >> fResult.floatVal;
b >> (uint8_t&)fResult.boolVal;
b >> fResult.strVal;
b >> (uint64_t&)fResult.decimalVal.value;

View File

@@ -200,6 +200,10 @@ public:
* ctor
*/
ConstantColumn(const std::string& sql, const double val);
/**
* ctor
*/
ConstantColumn(const std::string& sql, const long double val);
/**
* ctor
*/

View File

@@ -592,6 +592,12 @@ void SimpleColumn::evaluate(Row& row, bool& isNull)
break;
}
case CalpontSystemCatalog::LONGDOUBLE:
{
fResult.longDoubleVal = row.getLongDoubleField(fInputIndex);
break;
}
case CalpontSystemCatalog::DECIMAL:
case CalpontSystemCatalog::UDECIMAL:
{

View File

@@ -249,6 +249,7 @@ struct Result
// when converting origIntVal
uint64_t dummy;
double doubleVal;
long double longDoubleVal;
float floatVal;
bool boolVal;
std::string strVal;
@@ -376,6 +377,10 @@ public:
{
return fResult.doubleVal;
}
virtual long double getLongDoubleVal(rowgroup::Row& row, bool& isNull)
{
return fResult.longDoubleVal;
}
virtual IDB_Decimal getDecimalVal(rowgroup::Row& row, bool& isNull)
{
return fResult.decimalVal;
@@ -517,6 +522,9 @@ inline bool TreeNode::getBoolVal()
case CalpontSystemCatalog::UDOUBLE:
return (fResult.doubleVal != 0);
case CalpontSystemCatalog::LONGDOUBLE:
return (fResult.longDoubleVal != 0);
case CalpontSystemCatalog::DECIMAL:
case CalpontSystemCatalog::UDECIMAL:
return (fResult.decimalVal.value != 0);
@@ -646,6 +654,40 @@ inline const std::string& TreeNode::getStrVal()
fResult.strVal += tmp;
}
// snprintf(tmp, 312, "%e", fResult.doubleVal);
// fResult.strVal = tmp;
}
break;
}
case CalpontSystemCatalog::LONGDOUBLE:
{
if ((fabsl(fResult.longDoubleVal) > (1.0 / IDB_pow[13])) &&
(fabsl(fResult.longDoubleVal) < (float) IDB_pow[15]))
{
snprintf(tmp, 312, "%Lf", fResult.longDoubleVal);
fResult.strVal = removeTrailing0(tmp, 312);
}
else
{
// MCOL-299 Print scientific with 9 mantissa and no + sign for exponent
int exponent = (int)floorl(log10( fabsl(fResult.longDoubleVal))); // This will round down the exponent
long double base = fResult.longDoubleVal * pow(10, -1.0 * exponent);
if (isnan(exponent) || isnan(base))
{
snprintf(tmp, 312, "%Lf", fResult.longDoubleVal);
fResult.strVal = removeTrailing0(tmp, 312);
}
else
{
snprintf(tmp, 312, "%.14Lf", base);
fResult.strVal = removeTrailing0(tmp, 312);
snprintf(tmp, 312, "e%02d", exponent);
fResult.strVal += tmp;
}
// snprintf(tmp, 312, "%e", fResult.doubleVal);
// fResult.strVal = tmp;
}
@@ -736,6 +778,9 @@ inline int64_t TreeNode::getIntVal()
case CalpontSystemCatalog::UDOUBLE:
return (int64_t)fResult.doubleVal;
case CalpontSystemCatalog::LONGDOUBLE:
return (int64_t)fResult.longDoubleVal;
case CalpontSystemCatalog::DECIMAL:
case CalpontSystemCatalog::UDECIMAL:
{
@@ -779,6 +824,9 @@ inline uint64_t TreeNode::getUintVal()
case CalpontSystemCatalog::UDOUBLE:
return (uint64_t)fResult.doubleVal;
case CalpontSystemCatalog::LONGDOUBLE:
return (uint64_t)fResult.longDoubleVal;
case CalpontSystemCatalog::DECIMAL:
case CalpontSystemCatalog::UDECIMAL:
{
@@ -843,6 +891,9 @@ inline float TreeNode::getFloatVal()
case CalpontSystemCatalog::UDOUBLE:
return (float)fResult.doubleVal;
case CalpontSystemCatalog::LONGDOUBLE:
return (float)fResult.doubleVal;
case CalpontSystemCatalog::DECIMAL:
{
return (fResult.decimalVal.value / pow((double)10, fResult.decimalVal.scale));
@@ -978,6 +1029,9 @@ inline IDB_Decimal TreeNode::getDecimalVal()
case CalpontSystemCatalog::UDOUBLE:
throw logging::InvalidConversionExcept("TreeNode::getDecimalVal: non-support conversion from double unsigned");
case CalpontSystemCatalog::LONGDOUBLE:
throw logging::InvalidConversionExcept("TreeNode::getDecimalVal: non-support conversion from long double");
case CalpontSystemCatalog::DECIMAL:
case CalpontSystemCatalog::UDECIMAL:
return fResult.decimalVal;

View File

@@ -583,6 +583,16 @@ void WindowFunctionColumn::evaluate(Row& row, bool& isNull)
break;
}
case CalpontSystemCatalog::LONGDOUBLE:
{
if (row.equals(LONGDOUBLENULL, fInputIndex))
isNull = true;
else
fResult.longDoubleVal = row.getLongDoubleField(fInputIndex);
break;
}
case CalpontSystemCatalog::DECIMAL:
case CalpontSystemCatalog::UDECIMAL:
{

View File

@@ -488,6 +488,12 @@ void GroupConcator::outputRow(std::ostringstream& oss, const rowgroup::Row& row)
break;
}
case CalpontSystemCatalog::LONGDOUBLE:
{
oss << setprecision(15) << row.getLongDoubleField(*i);
break;
}
case CalpontSystemCatalog::FLOAT:
case CalpontSystemCatalog::UFLOAT:
{
@@ -632,6 +638,7 @@ int64_t GroupConcator::lengthEstimate(const rowgroup::Row& row)
case CalpontSystemCatalog::UDOUBLE:
case CalpontSystemCatalog::FLOAT:
case CalpontSystemCatalog::UFLOAT:
case CalpontSystemCatalog::LONGDOUBLE:
{
fieldLen = 1; // minimum length
break;

View File

@@ -838,6 +838,17 @@ bool compatibleColumnTypes(const CalpontSystemCatalog::ColDataType& dt1, uint32_
break;
case CalpontSystemCatalog::LONGDOUBLE:
if (forJoin && (dt2 != CalpontSystemCatalog::LONGDOUBLE))
return false;
else if (dt2 != CalpontSystemCatalog::FLOAT &&
dt2 != CalpontSystemCatalog::DOUBLE &&
dt2 != CalpontSystemCatalog::UFLOAT &&
dt2 != CalpontSystemCatalog::UDOUBLE &&
dt2 != CalpontSystemCatalog::LONGDOUBLE) return false;
break;
default:
return false;
break;

View File

@@ -25,6 +25,7 @@
#include <stdint.h>
#include <string>
#include <cmath>
namespace joblist
{
@@ -51,6 +52,7 @@ const uint32_t FLOATNULL = 0xFFAAAAAA;
const uint32_t FLOATEMPTYROW = 0xFFAAAAAB;
const uint64_t DOUBLENULL = 0xFFFAAAAAAAAAAAAAULL;
const uint64_t DOUBLEEMPTYROW = 0xFFFAAAAAAAAAAAABULL;
const long double LONGDOUBLENULL = nanl("");
const uint32_t DATENULL = 0xFFFFFFFE;
const uint32_t DATEEMPTYROW = 0xFFFFFFFF;

View File

@@ -266,6 +266,9 @@ inline string colTypeIdString(CalpontSystemCatalog::ColDataType type)
case CalpontSystemCatalog::DOUBLE:
return string("DOUBLE");
case CalpontSystemCatalog::LONGDOUBLE:
return string("LONGDOUBLE");
case CalpontSystemCatalog::DATETIME:
return string("DATETIME");

View File

@@ -250,6 +250,12 @@ void TupleConstantStep::constructContanstRow(const JobInfo& jobInfo)
break;
}
case CalpontSystemCatalog::LONGDOUBLE:
{
fRowConst.setLongDoubleField(c.longDoubleVal, *i);
break;
}
case CalpontSystemCatalog::CHAR:
case CalpontSystemCatalog::VARCHAR:
case CalpontSystemCatalog::TEXT:

View File

@@ -510,6 +510,22 @@ void TupleUnion::normalize(const Row& in, Row* out)
break;
}
case CalpontSystemCatalog::LONGDOUBLE:
{
int scale = in.getScale(i);
if (scale != 0)
{
long double d = in.getIntField(i);
d /= (uint64_t) pow(10.0, scale);
out->setLongDoubleField(d, i);
}
else
out->setLongDoubleField(in.getIntField(i), i);
break;
}
case CalpontSystemCatalog::DECIMAL:
case CalpontSystemCatalog::UDECIMAL:
{
@@ -620,6 +636,22 @@ dec1:
break;
}
case CalpontSystemCatalog::LONGDOUBLE:
{
int scale = in.getScale(i);
if (scale != 0)
{
long double d = in.getUintField(i);
d /= (uint64_t) pow(10.0, scale);
out->setLongDoubleField(d, i);
}
else
out->setLongDoubleField(in.getUintField(i), i);
break;
}
case CalpontSystemCatalog::DECIMAL:
case CalpontSystemCatalog::UDECIMAL:
{
@@ -804,6 +836,10 @@ dec2:
out->setDoubleField(val, i);
break;
case CalpontSystemCatalog::LONGDOUBLE:
out->setLongDoubleField(val, i);
break;
case CalpontSystemCatalog::CHAR:
case CalpontSystemCatalog::TEXT:
case CalpontSystemCatalog::VARCHAR:
@@ -842,6 +878,83 @@ dec3: /* have to pick a scale to use for the double. using 5... */
break;
}
case CalpontSystemCatalog::LONGDOUBLE:
{
long double val = in.getLongDoubleField(i);
switch (out->getColTypes()[i])
{
case CalpontSystemCatalog::TINYINT:
case CalpontSystemCatalog::SMALLINT:
case CalpontSystemCatalog::MEDINT:
case CalpontSystemCatalog::INT:
case CalpontSystemCatalog::BIGINT:
if (out->getScale(i))
goto dec4;
out->setIntField((int64_t) val, i);
break;
case CalpontSystemCatalog::UTINYINT:
case CalpontSystemCatalog::USMALLINT:
case CalpontSystemCatalog::UMEDINT:
case CalpontSystemCatalog::UINT:
case CalpontSystemCatalog::UBIGINT:
out->setUintField((uint64_t) val, i);
break;
case CalpontSystemCatalog::FLOAT:
case CalpontSystemCatalog::UFLOAT:
out->setFloatField(val, i);
break;
case CalpontSystemCatalog::DOUBLE:
case CalpontSystemCatalog::UDOUBLE:
out->setDoubleField(val, i);
break;
case CalpontSystemCatalog::LONGDOUBLE:
out->setLongDoubleField(val, i);
break;
case CalpontSystemCatalog::CHAR:
case CalpontSystemCatalog::TEXT:
case CalpontSystemCatalog::VARCHAR:
{
ostringstream os;
os.precision(15); // to match mysql's output
os << val;
out->setStringField(os.str(), i);
break;
}
case CalpontSystemCatalog::DECIMAL:
case CalpontSystemCatalog::UDECIMAL:
{
dec4: /* have to pick a scale to use for the double. using 5... */
uint32_t scale = 5;
uint64_t ival = (uint64_t) (double) (val * pow((double) 10, (double) scale));
int diff = out->getScale(i) - scale;
if (diff < 0)
ival /= (uint64_t) pow((double) 10, (double) - diff);
else
ival *= (uint64_t) pow((double) 10, (double) diff);
out->setIntField((int64_t) val, i);
break;
}
default:
ostringstream os;
os << "TupleUnion::normalize(): tried an illegal conversion: floating point to "
<< out->getColTypes()[i];
throw logic_error(os.str());
}
break;
}
case CalpontSystemCatalog::DECIMAL:
case CalpontSystemCatalog::UDECIMAL:
{
@@ -882,12 +995,20 @@ dec3: /* have to pick a scale to use for the double. using 5... */
}
case CalpontSystemCatalog::DOUBLE:
case CalpontSystemCatalog::UDOUBLE:
{
double dval = ((double) val) / IDB_pow[scale];
out->setDoubleField(dval, i);
break;
}
case CalpontSystemCatalog::LONGDOUBLE:
{
long double dval = ((long double) val) / IDB_pow[scale];
out->setLongDoubleField(dval, i);
break;
}
case CalpontSystemCatalog::CHAR:
case CalpontSystemCatalog::TEXT:
case CalpontSystemCatalog::VARCHAR:

View File

@@ -1173,6 +1173,20 @@ int ha_calpont_impl_write_batch_row_(uchar* buf, TABLE* table, cal_impl_if::cal_
break;
}
case CalpontSystemCatalog::LONGDOUBLE:
{
if (nullVal && (ci.columnTypes[colpos].constraintType != CalpontSystemCatalog::NOTNULL_CONSTRAINT))
fprintf(ci.filePtr, "%c", ci.delimiter);
else
{
fprintf(ci.filePtr, "%.15Lg%c", *((long double*)buf), ci.delimiter);
//printf("%.15g|", *((double*)buf));
}
buf += 8;
break;
}
case CalpontSystemCatalog::DECIMAL:
case CalpontSystemCatalog::UDECIMAL:
{

View File

@@ -4499,7 +4499,11 @@ ReturnedColumn* buildAggregateColumn(Item* item, gp_walk_info& gwi)
isp->sum_func() == Item_sum::AVG_DISTINCT_FUNC)
{
CalpontSystemCatalog::ColType ct = parm->resultType();
ct.colDataType = CalpontSystemCatalog::LONGDOUBLE;
ct.scale += 4;
// ct.colWidth = 8;
#if 0
switch (ct.colDataType)
{
case CalpontSystemCatalog::TINYINT:
@@ -4514,13 +4518,9 @@ ReturnedColumn* buildAggregateColumn(Item* item, gp_walk_info& gwi)
case CalpontSystemCatalog::UMEDINT:
case CalpontSystemCatalog::UINT:
case CalpontSystemCatalog::UBIGINT:
ct.colDataType = CalpontSystemCatalog::DECIMAL;
ct.colWidth = 8;
ct.scale += 4;
break;
#if PROMOTE_FLOAT_TO_DOUBLE_ON_SUM
case CalpontSystemCatalog::FLOAT:
case CalpontSystemCatalog::UFLOAT:
case CalpontSystemCatalog::DOUBLE:
@@ -4528,12 +4528,11 @@ ReturnedColumn* buildAggregateColumn(Item* item, gp_walk_info& gwi)
ct.colDataType = CalpontSystemCatalog::DOUBLE;
ct.colWidth = 8;
break;
#endif
default:
break;
}
#endif
ac->resultType(ct);
}
else if (isp->sum_func() == Item_sum::COUNT_FUNC ||
@@ -4549,7 +4548,9 @@ ReturnedColumn* buildAggregateColumn(Item* item, gp_walk_info& gwi)
isp->sum_func() == Item_sum::SUM_DISTINCT_FUNC)
{
CalpontSystemCatalog::ColType ct = parm->resultType();
ct.colDataType = CalpontSystemCatalog::LONGDOUBLE;
ct.scale += 4;
#if 0
switch (ct.colDataType)
{
case CalpontSystemCatalog::TINYINT:
@@ -4589,7 +4590,7 @@ ReturnedColumn* buildAggregateColumn(Item* item, gp_walk_info& gwi)
default:
break;
}
#endif
ac->resultType(ct);
}
else if (isp->sum_func() == Item_sum::STD_FUNC ||

View File

@@ -703,6 +703,41 @@ int fetchNextRow(uchar* buf, cal_table_info& ti, cal_connection_info* ci, bool h
//break;
}
case CalpontSystemCatalog::LONGDOUBLE:
{
double dl = row.getLongDoubleField(s);
if (dl == std::numeric_limits<long double>::infinity())
continue;
Field_double* f2 = (Field_double*)*f;
// bug 3483, reserve enough space for the longest double value
// -1.7976931348623157E+308 to -2.2250738585072014E-308, 0, and
// 2.2250738585072014E-308 to 1.7976931348623157E+308.
(*f)->field_length = 310;
//double double_val = *(double*)(&value);
//f2->store(double_val);
if ((f2->decimals() == DECIMAL_NOT_SPECIFIED && row.getScale(s) > 0)
|| f2->decimals() < row.getScale(s))
{
f2->dec = row.getScale(s);
}
f2->store(dl);
if ((*f)->null_ptr)
*(*f)->null_ptr &= ~(*f)->null_bit;
break;
//int64_t* icvp = (int64_t*)&dl;
//intColVal = *icvp;
//storeNumericField(f, intColVal, colType);
//break;
}
case CalpontSystemCatalog::DECIMAL:
case CalpontSystemCatalog::UDECIMAL:
{

View File

@@ -190,6 +190,9 @@ string name(CalpontSystemCatalog::ColType& ct)
case CalpontSystemCatalog::UDOUBLE:
return "UDOUBLE";
case CalpontSystemCatalog::LONGDOUBLE:
return "LONGDOUBLE";
default:
return "Unknown Type";
}