From 654d282088e05183fb2692d34a3040b33b04330e Mon Sep 17 00:00:00 2001 From: Jan Steemann Date: Mon, 23 Feb 2015 22:27:02 +0100 Subject: [PATCH 1/3] added fpconv_dtoa from https://github.com/night-shift/fpconv/ --- UnitTests/Basics/string-buffer-test.cpp | 6 +- arangod/Aql/Functions.cpp | 31 ++- lib/Basics/conversions.cpp | 7 +- lib/Basics/conversions.h | 4 +- lib/Basics/fpconv.cpp | 332 ++++++++++++++++++++++++ lib/Basics/fpconv.h | 33 +++ lib/Basics/powers.h | 87 +++++++ lib/Basics/string-buffer.cpp | 181 +------------ lib/CMakeLists.txt | 1 + lib/Makefile.files | 1 + 10 files changed, 492 insertions(+), 191 deletions(-) create mode 100644 lib/Basics/fpconv.cpp create mode 100644 lib/Basics/fpconv.h create mode 100644 lib/Basics/powers.h diff --git a/UnitTests/Basics/string-buffer-test.cpp b/UnitTests/Basics/string-buffer-test.cpp index 21bbed3aea..4ff3e3611f 100644 --- a/UnitTests/Basics/string-buffer-test.cpp +++ b/UnitTests/Basics/string-buffer-test.cpp @@ -704,12 +704,12 @@ BOOST_AUTO_TEST_CASE (tst_doubles) { value = n * n * n * n; TRI_ClearStringBuffer(&sb); TRI_AppendDoubleStringBuffer(&sb, value); - BOOST_CHECK_EQUAL("3575783498001355000000", sb._buffer); - + BOOST_CHECK_EQUAL("3575783498001355400000", sb._buffer); + value *= -1.0; TRI_ClearStringBuffer(&sb); TRI_AppendDoubleStringBuffer(&sb, value); - BOOST_CHECK_EQUAL("-3575783498001355000000", sb._buffer); + BOOST_CHECK_EQUAL("-3575783498001355400000", sb._buffer); TRI_DestroyStringBuffer(&sb); } diff --git a/arangod/Aql/Functions.cpp b/arangod/Aql/Functions.cpp index 4fb043b72d..0708714fad 100644 --- a/arangod/Aql/Functions.cpp +++ b/arangod/Aql/Functions.cpp @@ -28,6 +28,7 @@ //////////////////////////////////////////////////////////////////////////////// #include "Aql/Functions.h" +#include "Basics/fpconv.h" #include "Basics/JsonHelper.h" #include "Utils/Exception.h" @@ -115,38 +116,48 @@ AqlValue Functions::Length (triagens::arango::AqlTransaction* trx, if (json != nullptr) { switch (json->_type) { case TRI_JSON_UNUSED: - case TRI_JSON_NULL: + case TRI_JSON_NULL: { length = strlen("null"); break; + } - case TRI_JSON_BOOLEAN: + case TRI_JSON_BOOLEAN: { length = (json->_value._boolean ? strlen("true") : strlen("false")); break; + } - case TRI_JSON_NUMBER: - try { - std::string toString = std::to_string(json->_value._number); - length = toString.size(); + case TRI_JSON_NUMBER: { + if (std::isnan(json->_value._number) || + ! std::isfinite(json->_value._number)) { + // invalid value + length = strlen("null"); } - catch (...) { + else { + // convert to a string representation of the number + char buffer[24]; + length = static_cast(fpconv_dtoa(json->_value._number, buffer)); } break; + } case TRI_JSON_STRING: - case TRI_JSON_STRING_REFERENCE: + case TRI_JSON_STRING_REFERENCE: { // return number of characters (not bytes) in string length = TRI_CharLengthUtf8String(json->_value._string.data); break; + } - case TRI_JSON_OBJECT: + case TRI_JSON_OBJECT: { // return number of attributes length = json->_value._objects._length / 2; break; + } - case TRI_JSON_ARRAY: + case TRI_JSON_ARRAY: { // return list length length = TRI_LengthArrayJson(json); break; + } } } diff --git a/lib/Basics/conversions.cpp b/lib/Basics/conversions.cpp index b984262123..d82ccfbbc1 100644 --- a/lib/Basics/conversions.cpp +++ b/lib/Basics/conversions.cpp @@ -29,8 +29,9 @@ #include "conversions.h" -#include "Basics/tri-strings.h" +#include "Basics/fpconv.h" #include "Basics/string-buffer.h" +#include "Basics/tri-strings.h" // ----------------------------------------------------------------------------- // --SECTION-- public functions for string to something @@ -715,9 +716,10 @@ char* TRI_StringUInt64 (uint64_t attr) { } //////////////////////////////////////////////////////////////////////////////// -/// @brief convert to string from double +/// @brief convert to string from double (currently not used) //////////////////////////////////////////////////////////////////////////////// +#if 0 char* TRI_StringDouble (double value) { TRI_string_buffer_t buffer; @@ -726,6 +728,7 @@ char* TRI_StringDouble (double value) { return buffer._buffer; } +#endif //////////////////////////////////////////////////////////////////////////////// /// @brief convert to hex string from uint32, using the specified buffer. diff --git a/lib/Basics/conversions.h b/lib/Basics/conversions.h index b5a49fec46..431b71d353 100644 --- a/lib/Basics/conversions.h +++ b/lib/Basics/conversions.h @@ -243,10 +243,12 @@ char* TRI_StringInt64 (int64_t); char* TRI_StringUInt64 (uint64_t); //////////////////////////////////////////////////////////////////////////////// -/// @brief convert to string from double +/// @brief convert to string from double (currently not used) //////////////////////////////////////////////////////////////////////////////// +#if 0 char* TRI_StringDouble (double); +#endif //////////////////////////////////////////////////////////////////////////////// /// @brief convert to a hex string from uint32, using the specified buffer. diff --git a/lib/Basics/fpconv.cpp b/lib/Basics/fpconv.cpp new file mode 100644 index 0000000000..3fbd55af01 --- /dev/null +++ b/lib/Basics/fpconv.cpp @@ -0,0 +1,332 @@ +#include +#include + +#include "fpconv.h" +#include "powers.h" + +#define fracmask 0x000FFFFFFFFFFFFFU +#define expmask 0x7FF0000000000000U +#define hiddenbit 0x0010000000000000U +#define signmask 0x8000000000000000U +#define expbias (1023 + 52) + +#define absv(n) ((n) < 0 ? -(n) : (n)) +#define minv(a, b) ((a) < (b) ? (a) : (b)) + +static uint64_t tens[] = { + 10000000000000000000U, 1000000000000000000U, 100000000000000000U, + 10000000000000000U, 1000000000000000U, 100000000000000U, + 10000000000000U, 1000000000000U, 100000000000U, + 10000000000U, 1000000000U, 100000000U, + 10000000U, 1000000U, 100000U, + 10000U, 1000U, 100U, + 10U, 1U +}; + +static inline uint64_t get_dbits(double d) +{ + union { + double dbl; + uint64_t i; + } dbl_bits = { d }; + + return dbl_bits.i; +} + +static Fp build_fp(double d) +{ + uint64_t bits = get_dbits(d); + + Fp fp; + fp.frac = bits & fracmask; + fp.exp = (bits & expmask) >> 52; + + if(fp.exp) { + fp.frac += hiddenbit; + fp.exp -= expbias; + + } else { + fp.exp = -expbias + 1; + } + + return fp; +} + +static void normalize(Fp* fp) +{ + while ((fp->frac & hiddenbit) == 0) { + fp->frac <<= 1; + fp->exp--; + } + + int shift = 64 - 52 - 1; + fp->frac <<= shift; + fp->exp -= shift; +} + +static void get_normalized_boundaries(Fp* fp, Fp* lower, Fp* upper) +{ + upper->frac = (fp->frac << 1) + 1; + upper->exp = fp->exp - 1; + + while ((upper->frac & (hiddenbit << 1)) == 0) { + upper->frac <<= 1; + upper->exp--; + } + + int u_shift = 64 - 52 - 2; + + upper->frac <<= u_shift; + upper->exp = upper->exp - u_shift; + + + int l_shift = fp->frac == hiddenbit ? 2 : 1; + + lower->frac = (fp->frac << l_shift) - 1; + lower->exp = fp->exp - l_shift; + + + lower->frac <<= lower->exp - upper->exp; + lower->exp = upper->exp; +} + +static Fp multiply(Fp* a, Fp* b) +{ + const uint64_t lomask = 0x00000000FFFFFFFF; + + uint64_t ah_bl = (a->frac >> 32) * (b->frac & lomask); + uint64_t al_bh = (a->frac & lomask) * (b->frac >> 32); + uint64_t al_bl = (a->frac & lomask) * (b->frac & lomask); + uint64_t ah_bh = (a->frac >> 32) * (b->frac >> 32); + + uint64_t tmp = (ah_bl & lomask) + (al_bh & lomask) + (al_bl >> 32); + /* round up */ + tmp += 1U << 31; + + Fp fp = { + ah_bh + (ah_bl >> 32) + (al_bh >> 32) + (tmp >> 32), + a->exp + b->exp + 64 + }; + + return fp; +} + +static void round_digit(char* digits, int ndigits, uint64_t delta, uint64_t rem, uint64_t kappa, uint64_t frac) +{ + while (rem < frac && delta - rem >= kappa && + (rem + kappa < frac || frac - rem > rem + kappa - frac)) { + + digits[ndigits - 1]--; + rem += kappa; + } +} + +static int generate_digits(Fp* fp, Fp* upper, Fp* lower, char* digits, int* K) +{ + uint64_t wfrac = upper->frac - fp->frac; + uint64_t delta = upper->frac - lower->frac; + + Fp one; + one.frac = 1ULL << -upper->exp; + one.exp = upper->exp; + + uint64_t part1 = upper->frac >> -one.exp; + uint64_t part2 = upper->frac & (one.frac - 1); + + int idx = 0, kappa = 10; + uint64_t* divp; + /* 1000000000 */ + for(divp = tens + 10; kappa > 0; divp++) { + + uint64_t div = *divp; + unsigned digit = part1 / div; + + if (digit || idx) { + digits[idx++] = digit + '0'; + } + + part1 -= digit * div; + kappa--; + + uint64_t tmp = (part1 <<-one.exp) + part2; + if (tmp <= delta) { + *K += kappa; + round_digit(digits, idx, delta, tmp, div << -one.exp, wfrac); + + return idx; + } + } + + /* 10 */ + uint64_t* unit = tens + 18; + + while(true) { + part2 *= 10; + delta *= 10; + kappa--; + + unsigned digit = part2 >> -one.exp; + if (digit || idx) { + digits[idx++] = digit + '0'; + } + + part2 &= one.frac - 1; + if (part2 < delta) { + *K += kappa; + round_digit(digits, idx, delta, part2, one.frac, wfrac * *unit); + + return idx; + } + + unit--; + } +} + +static int grisu2(double d, char* digits, int* K) +{ + Fp w = build_fp(d); + + Fp lower, upper; + get_normalized_boundaries(&w, &lower, &upper); + + normalize(&w); + + int k; + Fp cp = find_cachedpow10(upper.exp, &k); + + w = multiply(&w, &cp); + upper = multiply(&upper, &cp); + lower = multiply(&lower, &cp); + + lower.frac++; + upper.frac--; + + *K = -k; + + return generate_digits(&w, &upper, &lower, digits, K); +} + +static int emit_digits(char* digits, int ndigits, char* dest, int K, bool neg) +{ + int exp = absv(K + ndigits - 1); + + /* write plain integer */ + if(K >= 0 && (exp < (ndigits + 7))) { + memcpy(dest, digits, ndigits); + memset(dest + ndigits, '0', K); + + return ndigits + K; + } + + /* write decimal w/o scientific notation */ + if(K < 0 && (K > -7 || exp < 4)) { + int offset = ndigits - absv(K); + /* fp < 1.0 -> write leading zero */ + if(offset <= 0) { + offset = -offset; + dest[0] = '0'; + dest[1] = '.'; + memset(dest + 2, '0', offset); + memcpy(dest + offset + 2, digits, ndigits); + + return ndigits + 2 + offset; + + /* fp > 1.0 */ + } else { + memcpy(dest, digits, offset); + dest[offset] = '.'; + memcpy(dest + offset + 1, digits + offset, ndigits - offset); + + return ndigits + 1; + } + } + + /* write decimal w/ scientific notation */ + ndigits = minv(ndigits, 18 - neg); + + int idx = 0; + dest[idx++] = digits[0]; + + if(ndigits > 1) { + dest[idx++] = '.'; + memcpy(dest + idx, digits + 1, ndigits - 1); + idx += ndigits - 1; + } + + dest[idx++] = 'e'; + + char sign = K + ndigits - 1 < 0 ? '-' : '+'; + dest[idx++] = sign; + + int cent = 0; + + if(exp > 99) { + cent = exp / 100; + dest[idx++] = cent + '0'; + exp -= cent * 100; + } + if(exp > 9) { + int dec = exp / 10; + dest[idx++] = dec + '0'; + exp -= dec * 10; + + } else if(cent) { + dest[idx++] = '0'; + } + + dest[idx++] = exp % 10 + '0'; + + return idx; +} + +static int filter_special(double fp, char* dest) +{ + if(fp == 0.0) { + dest[0] = '0'; + return 1; + } + + uint64_t bits = get_dbits(fp); + + bool nan = (bits & expmask) == expmask; + + if(!nan) { + return 0; + } + + if(bits & fracmask) { + dest[0] = 'n'; dest[1] = 'a'; dest[2] = 'n'; + + } else { + dest[0] = 'i'; dest[1] = 'n'; dest[2] = 'f'; + } + + return 3; +} + +int fpconv_dtoa(double d, char dest[24]) +{ + char digits[18]; + + int str_len = 0; + bool neg = false; + + if(get_dbits(d) & signmask) { + dest[0] = '-'; + str_len++; + neg = true; + } + + int spec = filter_special(d, dest + str_len); + + if(spec) { + return str_len + spec; + } + + int K = 0; + int ndigits = grisu2(d, digits, &K); + + str_len += emit_digits(digits, ndigits, dest + str_len, K, neg); + + return str_len; +} diff --git a/lib/Basics/fpconv.h b/lib/Basics/fpconv.h new file mode 100644 index 0000000000..58bbcccbb1 --- /dev/null +++ b/lib/Basics/fpconv.h @@ -0,0 +1,33 @@ +#ifndef FPCONV_H +#define FPCONV_H + +/* Fast and accurate double to string conversion based on Florian Loitsch's + * Grisu-algorithm[1]. + * + * Input: + * fp -> the double to convert, dest -> destination buffer. + * The generated string will never be longer than 24 characters. + * Make sure to pass a pointer to at least 24 bytes of memory. + * The emitted string will not be null terminated. + * + * Output: + * The number of written characters. + * + * Exemplary usage: + * + * void print(double d) + * { + * char buf[24 + 1] // plus null terminator + * int str_len = fpconv_dtoa(d, buf); + * + * buf[str_len] = '\0'; + * printf("%s", buf); + * } + * + */ + +int fpconv_dtoa(double fp, char dest[24]); + +#endif + +/* [1] http://florian.loitsch.com/publications/dtoa-pldi2010.pdf */ diff --git a/lib/Basics/powers.h b/lib/Basics/powers.h new file mode 100644 index 0000000000..c707eedb07 --- /dev/null +++ b/lib/Basics/powers.h @@ -0,0 +1,87 @@ +#include + +#define npowers 87 +#define steppowers 8 +#define firstpower -348 /* 10 ^ -348 */ + +#define expmax -32 +#define expmin -60 + + +typedef struct Fp { + uint64_t frac; + int exp; +} Fp; + +static Fp powers_ten[] = { + { 18054884314459144840U, -1220 }, { 13451937075301367670U, -1193 }, + { 10022474136428063862U, -1166 }, { 14934650266808366570U, -1140 }, + { 11127181549972568877U, -1113 }, { 16580792590934885855U, -1087 }, + { 12353653155963782858U, -1060 }, { 18408377700990114895U, -1034 }, + { 13715310171984221708U, -1007 }, { 10218702384817765436U, -980 }, + { 15227053142812498563U, -954 }, { 11345038669416679861U, -927 }, + { 16905424996341287883U, -901 }, { 12595523146049147757U, -874 }, + { 9384396036005875287U, -847 }, { 13983839803942852151U, -821 }, + { 10418772551374772303U, -794 }, { 15525180923007089351U, -768 }, + { 11567161174868858868U, -741 }, { 17236413322193710309U, -715 }, + { 12842128665889583758U, -688 }, { 9568131466127621947U, -661 }, + { 14257626930069360058U, -635 }, { 10622759856335341974U, -608 }, + { 15829145694278690180U, -582 }, { 11793632577567316726U, -555 }, + { 17573882009934360870U, -529 }, { 13093562431584567480U, -502 }, + { 9755464219737475723U, -475 }, { 14536774485912137811U, -449 }, + { 10830740992659433045U, -422 }, { 16139061738043178685U, -396 }, + { 12024538023802026127U, -369 }, { 17917957937422433684U, -343 }, + { 13349918974505688015U, -316 }, { 9946464728195732843U, -289 }, + { 14821387422376473014U, -263 }, { 11042794154864902060U, -236 }, + { 16455045573212060422U, -210 }, { 12259964326927110867U, -183 }, + { 18268770466636286478U, -157 }, { 13611294676837538539U, -130 }, + { 10141204801825835212U, -103 }, { 15111572745182864684U, -77 }, + { 11258999068426240000U, -50 }, { 16777216000000000000U, -24 }, + { 12500000000000000000U, 3 }, { 9313225746154785156U, 30 }, + { 13877787807814456755U, 56 }, { 10339757656912845936U, 83 }, + { 15407439555097886824U, 109 }, { 11479437019748901445U, 136 }, + { 17105694144590052135U, 162 }, { 12744735289059618216U, 189 }, + { 9495567745759798747U, 216 }, { 14149498560666738074U, 242 }, + { 10542197943230523224U, 269 }, { 15709099088952724970U, 295 }, + { 11704190886730495818U, 322 }, { 17440603504673385349U, 348 }, + { 12994262207056124023U, 375 }, { 9681479787123295682U, 402 }, + { 14426529090290212157U, 428 }, { 10748601772107342003U, 455 }, + { 16016664761464807395U, 481 }, { 11933345169920330789U, 508 }, + { 17782069995880619868U, 534 }, { 13248674568444952270U, 561 }, + { 9871031767461413346U, 588 }, { 14708983551653345445U, 614 }, + { 10959046745042015199U, 641 }, { 16330252207878254650U, 667 }, + { 12166986024289022870U, 694 }, { 18130221999122236476U, 720 }, + { 13508068024458167312U, 747 }, { 10064294952495520794U, 774 }, + { 14996968138956309548U, 800 }, { 11173611982879273257U, 827 }, + { 16649979327439178909U, 853 }, { 12405201291620119593U, 880 }, + { 9242595204427927429U, 907 }, { 13772540099066387757U, 933 }, + { 10261342003245940623U, 960 }, { 15290591125556738113U, 986 }, + { 11392378155556871081U, 1013 }, { 16975966327722178521U, 1039 }, + { 12648080533535911531U, 1066 } +}; + +static Fp find_cachedpow10(int exp, int* k) +{ + const double one_log_ten = 0.30102999566398114; + + int approx = -(exp + npowers) * one_log_ten; + int idx = (approx - firstpower) / steppowers; + + while(1) { + int current = exp + powers_ten[idx].exp + 64; + + if(current < expmin) { + idx++; + continue; + } + + if(current > expmax) { + idx--; + continue; + } + + *k = (firstpower + idx * steppowers); + + return powers_ten[idx]; + } +} diff --git a/lib/Basics/string-buffer.cpp b/lib/Basics/string-buffer.cpp index 0a77392253..2d90cd7d8b 100644 --- a/lib/Basics/string-buffer.cpp +++ b/lib/Basics/string-buffer.cpp @@ -28,8 +28,8 @@ //////////////////////////////////////////////////////////////////////////////// #include "string-buffer.h" -#include #include "Basics/conversions.h" +#include "Basics/fpconv.h" #include "Zip/zip.h" // ----------------------------------------------------------------------------- @@ -1282,10 +1282,7 @@ int TRI_AppendSizeHexStringBuffer (TRI_string_buffer_t * self, size_t attr) { //////////////////////////////////////////////////////////////////////////////// int TRI_AppendDoubleStringBuffer (TRI_string_buffer_t * self, double attr) { - // IEEE754 NaN values have an interesting property that we can exploit... - // if the architecture does not use IEEE754 values then this shouldn't do - // any harm either - if (attr != attr) { + if (std::isnan(attr)) { return TRI_AppendStringStringBuffer(self, "NaN"); } @@ -1296,182 +1293,16 @@ int TRI_AppendDoubleStringBuffer (TRI_string_buffer_t * self, double attr) { return TRI_AppendStringStringBuffer(self, "-inf"); } - int res = Reserve(self, 1); + int res = Reserve(self, 24); if (res != TRI_ERROR_NO_ERROR) { return res; } - if (attr < 0.0) { - AppendChar(self, '-'); - attr = -attr; - } - else if (attr == 0.0) { - AppendChar(self, '0'); - return TRI_ERROR_NO_ERROR; - } + int length = fpconv_dtoa(attr, self->_current); + self->_current += static_cast(length); - if (((double)((uint32_t) attr)) == attr) { - return TRI_AppendUInt32StringBuffer(self, (uint32_t) attr); - } - else if (attr < (double) 429496U) { - uint32_t smll; - - smll = (uint32_t)(attr * 10000.0); - - if (((double) smll) == attr * 10000.0) { - uint32_t ep; - - TRI_AppendUInt32StringBuffer(self, smll / 10000); - - ep = smll % 10000; - - if (ep != 0) { - size_t pos; - char a1; - char a2; - char a3; - char a4; - - pos = 0; - - res = Reserve(self, 6); - - if (res != TRI_ERROR_NO_ERROR) { - return res; - } - - AppendChar(self, '.'); - - if ((ep / 1000L) % 10 != 0) pos = 1; - a1 = (char) ((ep / 1000L) % 10 + '0'); - - if ((ep / 100L) % 10 != 0) pos = 2; - a2 = (char) ((ep / 100L) % 10 + '0'); - - if ((ep / 10L) % 10 != 0) pos = 3; - a3 = (char) ((ep / 10L) % 10 + '0'); - - if (ep % 10 != 0) pos = 4; - a4 = (char) (ep % 10 + '0'); - - AppendChar(self, a1); - if (pos > 1) { AppendChar(self, a2); } - if (pos > 2) { AppendChar(self, a3); } - if (pos > 3) { AppendChar(self, a4); } - - } - - return TRI_ERROR_NO_ERROR; - } - } - - // we do not habe a small integral number nor small decimal number with only a few decimal digits - - // there at most 16 significant digits, first find out if we have an integer value - if (10000000000000000.0 < attr) { - size_t n; - - n = 0; - - while (10000000000000000.0 < attr) { - attr /= 10.0; - ++n; - } - - res = TRI_AppendUInt64StringBuffer(self, (uint64_t) attr); - - if (res != TRI_ERROR_NO_ERROR) { - return res; - } - - res = Reserve(self, n); - - if (res != TRI_ERROR_NO_ERROR) { - return res; - } - - for (; 0 < n; --n) { - AppendChar(self, '0'); - } - - return TRI_ERROR_NO_ERROR; - } - - - // very small, i. e. less than 1 - else if (attr < 1.0) { - size_t n; - - n = 0; - - while (attr < 1.0) { - attr *= 10.0; - ++n; - - // should not happen, so it must be almost 0 - if (n > 400) { - return TRI_AppendUInt32StringBuffer(self, 0); - } - } - - res = Reserve(self, n + 2); - - if (res != TRI_ERROR_NO_ERROR) { - return res; - } - - AppendChar(self, '0'); - AppendChar(self, '.'); - - for (--n; 0 < n; --n) { - AppendChar(self, '0'); - } - - attr = 10000000000000000.0 * attr; - - return TRI_AppendUInt64StringBuffer(self, (uint64_t) attr); - } - - - // somewhere in between - else { - uint64_t m; - double d; - size_t n; - - m = (uint64_t) attr; - d = attr - m; - n = 0; - - TRI_AppendUInt64StringBuffer(self, m); - - while (d < 1.0) { - d *= 10.0; - ++n; - - // should not happen, so it must be almost 0 - if (n > 400) { - return TRI_ERROR_NO_ERROR; - } - } - - res = Reserve(self, n + 1); - - if (res != TRI_ERROR_NO_ERROR) { - return res; - } - - AppendChar(self, '.'); - - for (--n; 0 < n; --n) { - AppendChar(self, '0'); - } - - d = 10000000000000000.0 * d; - - return TRI_AppendUInt64StringBuffer(self, (uint64_t) d); - } + return TRI_ERROR_NO_ERROR; } // ----------------------------------------------------------------------------- diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index c6dab23a67..3ac17c493e 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -58,6 +58,7 @@ add_library( Basics/fasthash.cpp Basics/files.cpp Basics/FileUtils.cpp + Basics/fpconv.cpp Basics/hashes.cpp Basics/init.cpp Basics/InitialiseBasics.cpp diff --git a/lib/Makefile.files b/lib/Makefile.files index fc03537d4e..e2e63dfb5f 100644 --- a/lib/Makefile.files +++ b/lib/Makefile.files @@ -25,6 +25,7 @@ lib_libarango_a_SOURCES = \ lib/Basics/fasthash.cpp \ lib/Basics/files.cpp \ lib/Basics/FileUtils.cpp \ + lib/Basics/fpconv.cpp \ lib/Basics/hashes.cpp \ lib/Basics/init.cpp \ lib/Basics/InitialiseBasics.cpp \ From ec9c84b7c075513a645ee97650204e3f3a9e8728 Mon Sep 17 00:00:00 2001 From: Jan Steemann Date: Tue, 24 Feb 2015 09:23:25 +0100 Subject: [PATCH 2/3] added tests --- UnitTests/Basics/fpconv-test.cpp | 451 +++++++++++++++++++++++++++++++ UnitTests/CMakeLists.txt | 1 + UnitTests/Makefile.unittests | 1 + lib/Basics/fpconv.cpp | 2 +- 4 files changed, 454 insertions(+), 1 deletion(-) create mode 100644 UnitTests/Basics/fpconv-test.cpp diff --git a/UnitTests/Basics/fpconv-test.cpp b/UnitTests/Basics/fpconv-test.cpp new file mode 100644 index 0000000000..0a0b7cc803 --- /dev/null +++ b/UnitTests/Basics/fpconv-test.cpp @@ -0,0 +1,451 @@ +//////////////////////////////////////////////////////////////////////////////// +/// @brief test suite for fpconv.cpp +/// +/// @file +/// +/// DISCLAIMER +/// +/// Copyright 2012 triagens GmbH, Cologne, Germany +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// +/// Copyright holder is triAGENS GmbH, Cologne, Germany +/// +/// @author Jan Steemann +/// @author Copyright 2015, triAGENS GmbH, Cologne, Germany +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include "Basics/StringBuffer.h" +#include "Basics/fpconv.h" +#include "Basics/json.h" +#include "Basics/string-buffer.h" + +using namespace triagens::basics; + +// ----------------------------------------------------------------------------- +// --SECTION-- setup / tear-down +// ----------------------------------------------------------------------------- + +struct CFpconvSetup { + CFpconvSetup () { + BOOST_TEST_MESSAGE("setup fpconv"); + } + + ~CFpconvSetup () { + BOOST_TEST_MESSAGE("tear-down fpconv"); + } +}; + +// ----------------------------------------------------------------------------- +// --SECTION-- test suite +// ----------------------------------------------------------------------------- + +//////////////////////////////////////////////////////////////////////////////// +/// @brief setup +//////////////////////////////////////////////////////////////////////////////// + +BOOST_FIXTURE_TEST_SUITE(CFpconvTest, CFpconvSetup) + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test nan +//////////////////////////////////////////////////////////////////////////////// + +BOOST_AUTO_TEST_CASE (tst_nan) { + char out[24]; + double value; + int length; + + value = NAN; + BOOST_CHECK_EQUAL(true, std::isnan(value)); + length = fpconv_dtoa(value, out); + + BOOST_CHECK_EQUAL(std::string("NaN"), std::string(out, length)); + + StringBuffer buf(TRI_UNKNOWN_MEM_ZONE); + buf.appendDecimal(value); + BOOST_CHECK_EQUAL(std::string("NaN"), std::string(buf.c_str(), buf.length())); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test infinity +//////////////////////////////////////////////////////////////////////////////// + +BOOST_AUTO_TEST_CASE (tst_inf) { + char out[24]; + double value; + int length; + + value = INFINITY; + BOOST_CHECK_EQUAL(false, std::isfinite(value)); + length = fpconv_dtoa(value, out); + + BOOST_CHECK_EQUAL(std::string("inf"), std::string(out, length)); + + StringBuffer buf(TRI_UNKNOWN_MEM_ZONE); + buf.appendDecimal(value); + BOOST_CHECK_EQUAL(std::string("inf"), std::string(buf.c_str(), buf.length())); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test huge val +//////////////////////////////////////////////////////////////////////////////// + +BOOST_AUTO_TEST_CASE (tst_huge_val) { + char out[24]; + double value; + int length; + + value = HUGE_VAL; + BOOST_CHECK_EQUAL(false, std::isfinite(value)); + length = fpconv_dtoa(value, out); + + BOOST_CHECK_EQUAL(std::string("inf"), std::string(out, length)); + + StringBuffer buf(TRI_UNKNOWN_MEM_ZONE); + buf.appendDecimal(value); + BOOST_CHECK_EQUAL(std::string("inf"), std::string(buf.c_str(), buf.length())); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test huge val +//////////////////////////////////////////////////////////////////////////////// + +BOOST_AUTO_TEST_CASE (tst_huge_val_neg) { + char out[24]; + double value; + int length; + + value = -HUGE_VAL; + BOOST_CHECK_EQUAL(false, std::isfinite(value)); + length = fpconv_dtoa(value, out); + + BOOST_CHECK_EQUAL(std::string("-inf"), std::string(out, length)); + + StringBuffer buf(TRI_UNKNOWN_MEM_ZONE); + buf.appendDecimal(value); + BOOST_CHECK_EQUAL(std::string("-inf"), std::string(buf.c_str(), buf.length())); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test zero +//////////////////////////////////////////////////////////////////////////////// + +BOOST_AUTO_TEST_CASE (tst_zero) { + char out[24]; + double value; + int length; + + value = 0; + length = fpconv_dtoa(value, out); + + BOOST_CHECK_EQUAL(std::string("0"), std::string(out, length)); + + StringBuffer buf(TRI_UNKNOWN_MEM_ZONE); + buf.appendDecimal(value); + BOOST_CHECK_EQUAL(std::string("0"), std::string(buf.c_str(), buf.length())); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test zero +//////////////////////////////////////////////////////////////////////////////// + +BOOST_AUTO_TEST_CASE (tst_zero_neg) { + char out[24]; + double value; + int length; + + value = -0; + length = fpconv_dtoa(value, out); + + BOOST_CHECK_EQUAL(std::string("0"), std::string(out, length)); + + StringBuffer buf(TRI_UNKNOWN_MEM_ZONE); + buf.appendDecimal(value); + BOOST_CHECK_EQUAL(std::string("0"), std::string(buf.c_str(), buf.length())); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test high +//////////////////////////////////////////////////////////////////////////////// + +BOOST_AUTO_TEST_CASE (tst_value_high) { + char out[24]; + double value; + int length; + + value = 4.32e261; + length = fpconv_dtoa(value, out); + + BOOST_CHECK_EQUAL(std::string("4.32e+261"), std::string(out, length)); + + StringBuffer buf(TRI_UNKNOWN_MEM_ZONE); + buf.appendDecimal(value); + BOOST_CHECK_EQUAL(std::string("4.32e+261"), std::string(buf.c_str(), buf.length())); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test low +//////////////////////////////////////////////////////////////////////////////// + +BOOST_AUTO_TEST_CASE (tst_value_low) { + char out[24]; + double value; + int length; + + value = -4.32e261; + length = fpconv_dtoa(value, out); + + BOOST_CHECK_EQUAL(std::string("-4.32e+261"), std::string(out, length)); + + StringBuffer buf(TRI_UNKNOWN_MEM_ZONE); + buf.appendDecimal(value); + BOOST_CHECK_EQUAL(std::string("-4.32e+261"), std::string(buf.c_str(), buf.length())); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test small +//////////////////////////////////////////////////////////////////////////////// + +BOOST_AUTO_TEST_CASE (tst_value_small) { + char out[24]; + double value; + int length; + + value = 4.32e-261; + length = fpconv_dtoa(value, out); + + BOOST_CHECK_EQUAL(std::string("4.32e-261"), std::string(out, length)); + + StringBuffer buf(TRI_UNKNOWN_MEM_ZONE); + buf.appendDecimal(value); + BOOST_CHECK_EQUAL(std::string("4.32e-261"), std::string(buf.c_str(), buf.length())); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test mchacki's value +//////////////////////////////////////////////////////////////////////////////// + +BOOST_AUTO_TEST_CASE (tst_value_mchacki1) { + char out[24]; + double value; + int length; + + value = 1.374; + length = fpconv_dtoa(value, out); + + BOOST_CHECK_EQUAL(std::string("1.374"), std::string(out, length)); + + StringBuffer buf(TRI_UNKNOWN_MEM_ZONE); + buf.appendDecimal(value); + BOOST_CHECK_EQUAL(std::string("1.374"), std::string(buf.c_str(), buf.length())); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test mchacki's value +//////////////////////////////////////////////////////////////////////////////// + +BOOST_AUTO_TEST_CASE (tst_value_mchacki2) { + char out[24]; + double value; + int length; + + value = 56.94837631946843; + length = fpconv_dtoa(value, out); + + BOOST_CHECK_EQUAL(std::string("56.94837631946843"), std::string(out, length)); + + StringBuffer buf(TRI_UNKNOWN_MEM_ZONE); + buf.appendDecimal(value); + BOOST_CHECK_EQUAL(std::string("56.94837631946843"), std::string(buf.c_str(), buf.length())); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test one third roundtrip +//////////////////////////////////////////////////////////////////////////////// + +BOOST_AUTO_TEST_CASE (tst_value_mchacki2_roundtrip) { + double value; + + value = 56.94837631946843; + + TRI_string_buffer_t buffer; + TRI_InitStringBuffer(&buffer, TRI_UNKNOWN_MEM_ZONE); + + auto json = TRI_CreateNumberJson(TRI_UNKNOWN_MEM_ZONE, value); + TRI_StringifyJson(&buffer, json); + + BOOST_CHECK_EQUAL(std::string("56.94837631946843"), std::string(buffer._buffer, buffer._current - buffer._buffer)); + + auto json2 = TRI_Json2String(TRI_UNKNOWN_MEM_ZONE, buffer._buffer, nullptr); + BOOST_CHECK_EQUAL(TRI_JSON_NUMBER, json2->_type); + BOOST_CHECK_EQUAL(value, json2->_value._number); + TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json2); + + TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json); + TRI_DestroyStringBuffer(&buffer); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test one third +//////////////////////////////////////////////////////////////////////////////// + +BOOST_AUTO_TEST_CASE (tst_one_third) { + char out[24]; + double value; + int length; + + value = 1.0 / 3.0; + length = fpconv_dtoa(value, out); + + BOOST_CHECK_EQUAL(std::string("0.3333333333333333"), std::string(out, length)); + + StringBuffer buf(TRI_UNKNOWN_MEM_ZONE); + buf.appendDecimal(value); + BOOST_CHECK_EQUAL(std::string("0.3333333333333333"), std::string(buf.c_str(), buf.length())); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test one third roundtrip +//////////////////////////////////////////////////////////////////////////////// + +BOOST_AUTO_TEST_CASE (tst_one_third_roundtrip) { + double value; + + value = 1.0 / 3.0; + + TRI_string_buffer_t buffer; + TRI_InitStringBuffer(&buffer, TRI_UNKNOWN_MEM_ZONE); + + auto json = TRI_CreateNumberJson(TRI_UNKNOWN_MEM_ZONE, value); + TRI_StringifyJson(&buffer, json); + + BOOST_CHECK_EQUAL(std::string("0.3333333333333333"), std::string(buffer._buffer, buffer._current - buffer._buffer)); + + auto json2 = TRI_Json2String(TRI_UNKNOWN_MEM_ZONE, buffer._buffer, nullptr); + BOOST_CHECK_EQUAL(TRI_JSON_NUMBER, json2->_type); + BOOST_CHECK_EQUAL(value, json2->_value._number); + TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json2); + + TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json); + TRI_DestroyStringBuffer(&buffer); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test 0.4 +//////////////////////////////////////////////////////////////////////////////// + +BOOST_AUTO_TEST_CASE (tst_04) { + char out[24]; + double value; + int length; + + value = 0.1 + 0.3; + length = fpconv_dtoa(value, out); + + BOOST_CHECK_EQUAL(std::string("0.4"), std::string(out, length)); + + StringBuffer buf(TRI_UNKNOWN_MEM_ZONE); + buf.appendDecimal(value); + BOOST_CHECK_EQUAL(std::string("0.4"), std::string(buf.c_str(), buf.length())); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test 0.4 +//////////////////////////////////////////////////////////////////////////////// + +BOOST_AUTO_TEST_CASE (tst_04_roundtrip) { + double value; + + value = 0.1 + 0.3; + + TRI_string_buffer_t buffer; + TRI_InitStringBuffer(&buffer, TRI_UNKNOWN_MEM_ZONE); + + auto json = TRI_CreateNumberJson(TRI_UNKNOWN_MEM_ZONE, value); + TRI_StringifyJson(&buffer, json); + + BOOST_CHECK_EQUAL(std::string("0.4"), std::string(buffer._buffer, buffer._current - buffer._buffer)); + + auto json2 = TRI_Json2String(TRI_UNKNOWN_MEM_ZONE, buffer._buffer, nullptr); + BOOST_CHECK_EQUAL(TRI_JSON_NUMBER, json2->_type); + BOOST_CHECK_EQUAL(value, json2->_value._number); + TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json2); + + TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json); + TRI_DestroyStringBuffer(&buffer); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test big roundtrip +//////////////////////////////////////////////////////////////////////////////// + +BOOST_AUTO_TEST_CASE (tst_value_high_roundtrip) { + double value; + + value = 4.32e261; + + TRI_string_buffer_t buffer; + TRI_InitStringBuffer(&buffer, TRI_UNKNOWN_MEM_ZONE); + + auto json = TRI_CreateNumberJson(TRI_UNKNOWN_MEM_ZONE, value); + TRI_StringifyJson(&buffer, json); + + BOOST_CHECK_EQUAL(std::string("4.32e+261"), std::string(buffer._buffer, buffer._current - buffer._buffer)); + + auto json2 = TRI_Json2String(TRI_UNKNOWN_MEM_ZONE, buffer._buffer, nullptr); + BOOST_CHECK_EQUAL(TRI_JSON_NUMBER, json2->_type); + BOOST_CHECK_EQUAL(value, json2->_value._number); + TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json2); + + TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json); + TRI_DestroyStringBuffer(&buffer); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test small roundtrip +//////////////////////////////////////////////////////////////////////////////// + +BOOST_AUTO_TEST_CASE (tst_value_low_roundtrip) { + double value; + + value = -4.32e261; + + TRI_string_buffer_t buffer; + TRI_InitStringBuffer(&buffer, TRI_UNKNOWN_MEM_ZONE); + + auto json = TRI_CreateNumberJson(TRI_UNKNOWN_MEM_ZONE, value); + TRI_StringifyJson(&buffer, json); + + BOOST_CHECK_EQUAL(std::string("-4.32e+261"), std::string(buffer._buffer, buffer._current - buffer._buffer)); + + auto json2 = TRI_Json2String(TRI_UNKNOWN_MEM_ZONE, buffer._buffer, nullptr); + BOOST_CHECK_EQUAL(TRI_JSON_NUMBER, json2->_type); + BOOST_CHECK_EQUAL(value, json2->_value._number); + TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json2); + + TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json); + TRI_DestroyStringBuffer(&buffer); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief generate tests +//////////////////////////////////////////////////////////////////////////////// + +BOOST_AUTO_TEST_SUITE_END () + +// Local Variables: +// mode: outline-minor +// outline-regexp: "^\\(/// @brief\\|/// {@inheritDoc}\\|/// @addtogroup\\|// --SECTION--\\|/// @\\}\\)" +// End: diff --git a/UnitTests/CMakeLists.txt b/UnitTests/CMakeLists.txt index ba28b06e80..d6bc10e5cc 100644 --- a/UnitTests/CMakeLists.txt +++ b/UnitTests/CMakeLists.txt @@ -40,6 +40,7 @@ add_executable( Basics/conversions-test.cpp Basics/csv-test.cpp Basics/files-test.cpp + Basics/fpconv-test.cpp Basics/json-test.cpp Basics/json-utilities-test.cpp Basics/hashes-test.cpp diff --git a/UnitTests/Makefile.unittests b/UnitTests/Makefile.unittests index 595b44b797..816b391df1 100755 --- a/UnitTests/Makefile.unittests +++ b/UnitTests/Makefile.unittests @@ -338,6 +338,7 @@ UnitTests_basics_suite_SOURCES = \ UnitTests/Basics/conversions-test.cpp \ UnitTests/Basics/csv-test.cpp \ UnitTests/Basics/files-test.cpp \ + UnitTests/Basics/fpconv-test.cpp \ UnitTests/Basics/json-test.cpp \ UnitTests/Basics/json-utilities-test.cpp \ UnitTests/Basics/hashes-test.cpp \ diff --git a/lib/Basics/fpconv.cpp b/lib/Basics/fpconv.cpp index 3fbd55af01..0b101425b2 100644 --- a/lib/Basics/fpconv.cpp +++ b/lib/Basics/fpconv.cpp @@ -295,7 +295,7 @@ static int filter_special(double fp, char* dest) } if(bits & fracmask) { - dest[0] = 'n'; dest[1] = 'a'; dest[2] = 'n'; + dest[0] = 'N'; dest[1] = 'a'; dest[2] = 'N'; } else { dest[0] = 'i'; dest[1] = 'n'; dest[2] = 'f'; From 78616caaf7b78fa3051962ac45062d11da7756e3 Mon Sep 17 00:00:00 2001 From: Jan Steemann Date: Tue, 24 Feb 2015 10:26:30 +0100 Subject: [PATCH 3/3] added test --- UnitTests/HttpInterface/api-cursor-spec.rb | 52 ++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/UnitTests/HttpInterface/api-cursor-spec.rb b/UnitTests/HttpInterface/api-cursor-spec.rb index f184aa7349..98e90d23da 100644 --- a/UnitTests/HttpInterface/api-cursor-spec.rb +++ b/UnitTests/HttpInterface/api-cursor-spec.rb @@ -456,6 +456,58 @@ describe ArangoDB do doc.parsed_response['code'].should eq(400) doc.parsed_response['errorNum'].should eq(1501) end + + end + +################################################################################ +## floating points +################################################################################ + + context "fetching floating-point values:" do + before do + @cn = "users" + ArangoDB.drop_collection(@cn) + @cid = ArangoDB.create_collection(@cn, false) + + ArangoDB.post("/_api/document?collection=#{@cid}", :body => "{ \"_key\" : \"big\", \"value\" : 4e+262 }") + ArangoDB.post("/_api/document?collection=#{@cid}", :body => "{ \"_key\" : \"neg\", \"value\" : -4e262 }") + ArangoDB.post("/_api/document?collection=#{@cid}", :body => "{ \"_key\" : \"pos\", \"value\" : 4e262 }") + ArangoDB.post("/_api/document?collection=#{@cid}", :body => "{ \"_key\" : \"small\", \"value\" : 4e-262 }") + end + + after do + ArangoDB.drop_collection(@cn) + end + + it "fetching via cursor" do + cmd = api + body = "{ \"query\" : \"FOR u IN #{@cn} SORT u._key RETURN u.value\" }" + doc = ArangoDB.log_post("#{prefix}-float", cmd, :body => body) + + doc.code.should eq(201) + doc.headers['content-type'].should eq("application/json; charset=utf-8") + doc.parsed_response['error'].should eq(false) + doc.parsed_response['code'].should eq(201) + doc.parsed_response['id'].should be_nil + result = doc.parsed_response['result'] + result.length.should eq(4) + result[0].should eq(4e262); + result[1].should eq(-4e262); + result[2].should eq(4e262); + result[3].should eq(4e-262); + + doc = ArangoDB.get("/_api/document/#{@cid}/big") + doc.parsed_response['value'].should eq(4e262) + + doc = ArangoDB.get("/_api/document/#{@cid}/neg") + doc.parsed_response['value'].should eq(-4e262) + + doc = ArangoDB.get("/_api/document/#{@cid}/pos") + doc.parsed_response['value'].should eq(4e262) + + doc = ArangoDB.get("/_api/document/#{@cid}/small") + doc.parsed_response['value'].should eq(4e-262) + end end end