Skip to content

Commit

Permalink
SERVER-23263 - Make Decimal128 toString() conform to new spec
Browse files Browse the repository at this point in the history
  • Loading branch information
Vincent Do authored and vincentdo committed May 31, 2016
1 parent 5df895a commit 5b6b62d
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 74 deletions.
107 changes: 60 additions & 47 deletions src/mongo/platform/decimal128.cpp
Expand Up @@ -404,81 +404,94 @@ std::string Decimal128::toString() const {
*/
bid128_to_string(decimalCharRepresentation, dec128, &idec_signaling_flags);

std::string dec128String(decimalCharRepresentation);
StringData dec128String(decimalCharRepresentation);

std::string::size_type ePos = dec128String.find("E");
int ePos = dec128String.find("E");

// Calculate the precision and exponent of the number and output it in a readable manner
int precision = 0;
int exponent = 0;
int stringReadPosition = 0;

std::string exponentString = dec128String.substr(ePos);
StringData exponentString = dec128String.substr(ePos);

// Get the value of the exponent, start at 2 to ignore the E and the sign
for (std::string::size_type i = 2; i < exponentString.size(); ++i) {
for (size_t i = 2; i < exponentString.size(); ++i) {
exponent = exponent * 10 + (exponentString[i] - '0');
}
if (exponentString[1] == '-') {
exponent *= -1;
}
// Get the total precision of the number
// Get the total precision of the number, i.e. the length of the coefficient
precision = dec128String.size() - exponentString.size() - 1 /* mantissa sign */;

std::string result;
// Initially result is set to equal just the sign of the dec128 string
// For formatting, leave off the sign if it is positive
if (dec128String[0] == '-')
result = "-";
stringReadPosition++;

int scientificExponent = precision - 1 + exponent;

// If the number is significantly large, small, or the user has specified an exponent
// such that converting to string would need to append trailing zeros, display the
// number in scientific notation
if (scientificExponent >= 12 || scientificExponent <= -4 || exponent > 0) {
// Output in scientific format
result += dec128String.substr(stringReadPosition, 1);
stringReadPosition++;
precision--;
if (precision)
result += ".";
result += dec128String.substr(stringReadPosition, precision);
// Add the exponent
result += "E";
if (scientificExponent > 0)
result += "+";
result += std::to_string(scientificExponent);

StringData coefficient = dec128String.substr(1, precision);
int adjustedExponent = exponent + precision - 1;

if (exponent > 0 || adjustedExponent < -6) {
result += _convertToScientificNotation(coefficient, adjustedExponent);
} else {
// Regular format with no decimal place
if (exponent >= 0) {
result += dec128String.substr(stringReadPosition, precision);
stringReadPosition += precision;
} else {
int radixPosition = precision + exponent;
if (radixPosition > 0) {
// Non-zero digits before radix point
result += dec128String.substr(stringReadPosition, radixPosition);
stringReadPosition += radixPosition;
} else {
// Leading zero before radix point
result += "0";
}
result += _convertToStandardDecimalNotation(coefficient, exponent);
}

result += ".";
// Leading zeros after radix point
while (radixPosition++ < 0)
result += "0";
return result;
}

result +=
dec128String.substr(stringReadPosition, precision - std::max(radixPosition - 1, 0));
std::string Decimal128::_convertToScientificNotation(StringData coefficient,
int adjustedExponent) const {
int cLength = coefficient.size();
std::string result;
for (int i = 0; i < cLength; i++) {
result += coefficient[i];
if (i == 0 && cLength > 1) {
result += '.';
}
}

result += 'E';
if (adjustedExponent > 0) {
result += '+';
}
result += std::to_string(adjustedExponent);
return result;
}

std::string Decimal128::_convertToStandardDecimalNotation(StringData coefficient,
int exponent) const {
if (exponent == 0) {
return coefficient.toString();
} else {
invariant(exponent < 0);
std::string result;
int precision = coefficient.size();
// Absolute value of the exponent
int significantDecimalDigits = -exponent;
bool decimalAppended = false;

// Pre-pend 0's before the coefficient as necessary
for (int i = precision; i <= significantDecimalDigits; i++) {
result += '0';
if (i == precision) {
result += '.';
decimalAppended = true;
}
}

// Copy over the digits in the coefficient
for (int i = 0; i < precision; i++) {
if (precision - i == significantDecimalDigits && !decimalAppended) {
result += '.';
}
result += coefficient[i];
}
return result;
}
}

bool Decimal128::isZero() const {
return bid128_isZero(decimal128ToLibraryType(_value));
}
Expand Down
3 changes: 3 additions & 0 deletions src/mongo/platform/decimal128.h
Expand Up @@ -398,6 +398,9 @@ class Decimal128 {
static const uint64_t kCombinationNaN = 0x1f << 12;
static const uint64_t kCanonicalCoefficientHighFieldMask = (1ull << 49) - 1;

std::string _convertToScientificNotation(StringData coefficient, int adjustedExponent) const;
std::string _convertToStandardDecimalNotation(StringData coefficient, int exponent) const;

uint64_t _getCombinationField() const {
return (_value.high64 >> kCombinationFieldPos) & kCombinationFieldMask;
}
Expand Down
65 changes: 38 additions & 27 deletions src/mongo/platform/decimal128_test.cpp
Expand Up @@ -119,7 +119,7 @@ TEST(Decimal128Test, TestDoubleConstructorQuant1) {
TEST(Decimal128Test, TestDoubleConstructorQuant2) {
double dbl = 0.1 / 10000;
Decimal128 d(dbl);
ASSERT_EQUALS(d.toString(), "1.00000000000000E-5");
ASSERT_EQUALS(d.toString(), "0.0000100000000000000");
}

TEST(Decimal128Test, TestDoubleConstructorQuant3) {
Expand Down Expand Up @@ -774,46 +774,57 @@ TEST(Decimal128Test, TestDecimal128ToStringInRangeNeg4Minus) {
ASSERT_EQUALS(result, "-0.005");
}

TEST(Decimal128Test, TestDecimal128ToStringOutRangeNeg1) {
std::string s = ".0005";
Decimal128 d(s);
std::string result = d.toString();
ASSERT_EQUALS(result, "5E-4");
}

TEST(Decimal128Test, TestDecimal128ToStringOutRangeNeg2) {
std::string s = ".000005123123123123";
Decimal128 d(s);
std::string result = d.toString();
ASSERT_EQUALS(result, "5.123123123123E-6");
}

TEST(Decimal128Test, TestDecimal128ToStringOutRangeNeg3) {
std::string s = ".012587E-200";
Decimal128 d(s);
std::string result = d.toString();
ASSERT_EQUALS(result, "1.2587E-202");
}

TEST(Decimal128Test, TestDecimal128ToStringOutRangePos1) {
std::string s = "1234567890123";
Decimal128 d(s);
std::string result = d.toString();
ASSERT_EQUALS(result, "1.234567890123E+12");
}

TEST(Decimal128Test, TestDecimal128ToStringOutRangePos2) {
std::string s = "10201.01E14";
Decimal128 d(s);
std::string result = d.toString();
ASSERT_EQUALS(result, "1.020101E+18");
}

TEST(Decimal128Test, TestDecimal128ToStringOutRangePos3) {
std::string s = "1234567890123456789012345678901234";
Decimal128 d(s);
std::string result = d.toString();
ASSERT_EQUALS(result, "1.234567890123456789012345678901234E+33");
TEST(Decimal128Test, TestDecimal128ToStringFinite) {
// General test cases taken from http://speleotrove.com/decimal/daconvs.html#reftostr
std::string s[15] = {"123",
"-123",
"123E1",
"123E3",
"123E-1",
"123E-5",
"123E-10",
"-123E-12",
"0E0",
"0E-2",
"0E2",
"-0",
"5E-6",
"50E-7",
"5E-7"};
std::string expected[15] = {"123",
"-123",
"1.23E+3",
"1.23E+5",
"12.3",
"0.00123",
"1.23E-8",
"-1.23E-10",
"0",
"0.00",
"0E+2",
"-0",
"0.000005",
"0.0000050",
"5E-7"};
for (int i = 0; i < 15; i++) {
Decimal128 d(s[i]);
std::string result = d.toString();
ASSERT_EQUALS(result, expected[i]);
}
}

TEST(Decimal128Test, TestDecimal128ToStringInvalidToNaN) {
Expand Down

0 comments on commit 5b6b62d

Please sign in to comment.