diff --git a/README.md b/README.md index d38beef..c817ea8 100644 --- a/README.md +++ b/README.md @@ -26,10 +26,10 @@ _OS:_ Linux Mint 17.2 64bit FIX message type | FIX specification | Validation | Average time to parse one message ----------------------------------|------------------------------------------|------------|---------------------------------- -NewOrderSingle('D') | Hand-coded spec. for this message only | No | 0.326 µs/msg -NewOrderSingle('D') | Hand-coded spec. for this message only | Yes | 0.547 µs/msg -NewOrderSingle('D') | Compiled full spec. for FIX.4.4 | Yes | 0.739 µs/msg -MarketDataIncrementalRefresh('X') | Hand-coded spec. for this message only | Yes | 1.263 µs/msg -MarketDataIncrementalRefresh('X') | Compiled full spec. for FIX.4.4 | Yes | 1.443 µs/msg +NewOrderSingle('D') | Hand-coded spec. for this message only | No | 0.317 µs/msg +NewOrderSingle('D') | Hand-coded spec. for this message only | Yes | 0.554 µs/msg +NewOrderSingle('D') | Compiled full spec. for FIX.4.4 | Yes | 0.757 µs/msg +MarketDataIncrementalRefresh('X') | Hand-coded spec. for this message only | Yes | 1.223 µs/msg +MarketDataIncrementalRefresh('X') | Compiled full spec. for FIX.4.4 | Yes | 1.386 µs/msg For more details see `doc/` directory of the project. diff --git a/src/converters.c b/src/converters.c index 290061c..a77e6d8 100644 --- a/src/converters.c +++ b/src/converters.c @@ -56,6 +56,47 @@ fix_error copy_fix_tag_as_string(const fix_group* const group, unsigned tag, cha return FE_OK; } +// ascii digits to long converters +static +const char* convert_significant_digits(const char* s, long* const result) +{ + long res = 0; + unsigned c = CHAR_TO_INT(*s) - '0'; + + if(c == 0) + return NULL; + + if(c <= 9) + { + res = c; + + for(c = CHAR_TO_INT(*++s) - '0'; c <= 9; c = CHAR_TO_INT(*++s) - '0') + { + const long t = res * 10 + c; + + if(t < res) // overflow + return NULL; + + res = t; + } + } + + // done + *result = res; + return s; +} + +static +const char* convert_digits(const char* s, long* const result) +{ + // skip leading zeroes + while(*s == '0') + ++s; + + // convert digits + return convert_significant_digits(s, result); +} + // tag as long integer fix_error get_fix_tag_as_long(const fix_group* const group, unsigned tag, long* const result) { @@ -70,31 +111,33 @@ fix_error get_fix_tag_as_long(const fix_group* const group, unsigned tag, long* if(err != FE_OK) return err; - // conversion - long val = 0; - const char* s = value.begin; - const int negative = (*s == '-') ? (++s, 1) : 0; + if(fix_string_length(value) > 20) // ??? + RETURN( FE_INVALID_VALUE ); - // quickly skip leading zeroes - while(*s == '0') - ++s; + // sign + bool neg = false; - // convert all significant digits - while(*s >= '0' && *s <= '9') + if(*value.begin == '-') { - const long new_val = val * 10L + *s++ - '0'; + ++value.begin; + neg = true; + } - if(new_val < val) // overflow - RETURN( FE_INVALID_VALUE ); + // conversion + long val; - val = new_val; - } + value.begin = convert_digits(value.begin, &val); - if(*s != SOH || s == value.begin + negative) // no SOH terminator or the sign only + // validation + if(!value.begin || (neg && val == 0)) // overflow or '-0' + RETURN( FE_INVALID_VALUE ); + + if(value.begin < value.end) // unprocessed bytes RETURN( FE_INCORRECT_VALUE_FORMAT ); + // all clear if(result) - *result = negative ? -val : val; + *result = neg ? -val : val; return FE_OK; } @@ -118,62 +161,70 @@ fix_error get_fix_tag_as_double(const fix_group* const group, unsigned tag, doub if(err != FE_OK) return err; - // conversion - long val = 0, frac = 0; - unsigned ndig = 0; // number of significant digits - const char* s = value.begin; - const double sign = (*s == '-') ? (++s, -1.) : 1.; - const char* mark = s; + // sign + bool neg = false; + + if(*value.begin == '-') + { + ++value.begin; + neg = true; + } // skip leading zeroes - while(*s == '0') - ++s; + while(*value.begin == '0') + ++value.begin; // integer part - while(*s >= '0' && *s <= '9') - { - if(++ndig > 15) - RETURN( FE_INVALID_VALUE ); + long int_part; + const char* s = convert_significant_digits(value.begin, &int_part); - val = val * 10L + *s++ - '0'; - } + if(!s) + RETURN( FE_INVALID_VALUE ); - if(s == mark) // cannot have empty integer part - RETURN( FE_INCORRECT_VALUE_FORMAT ); + unsigned nsig = s - value.begin; // significant digits counter - // fractional part + if(nsig > 15) + RETURN( FE_INVALID_VALUE ); + + long frac_part = 0; unsigned nfrac = 0; - if(*s == '.') + if(*s == '.' && *++s != SOH) { - mark = ++s; + // fractional part + value.begin = s; + s = convert_digits(s, &frac_part); - if(ndig == 0) - while(*s == '0') // skip leading zeroes - ++s; + if(!s) + RETURN( FE_INCORRECT_VALUE_FORMAT ); - while(*s >= '0' && *s <= '9') - { - if(++ndig > 15) - RETURN( FE_INVALID_VALUE ); + nfrac = s - value.begin; - frac = frac * 10L + *s++ - '0'; - } - - nfrac = s - mark; + if(nsig + nfrac > 15) // counting trailing zeros as significant, contrary to the definition + RETURN( FE_INCORRECT_VALUE_FORMAT ); } - if(*s != SOH) + // final checks + if(s < value.end) // unprocessed bytes RETURN( FE_INCORRECT_VALUE_FORMAT ); - static const double mult[] = { 0., 1e-1, 1e-2, 1e-3, 1e-4, 1e-5, 1e-6, 1e-7, 1e-8, 1e-9, 1e-10, 1e-11, 1e-12, 1e-13, 1e-14, 1e-15 }; + if(neg && int_part == 0 && frac_part == 0) // -0.0 + RETURN( FE_INVALID_VALUE ); + + // compose result + static const double factor[] = { 0., 1e-1, 1e-2, 1e-3, 1e-4, 1e-5, 1e-6, 1e-7, 1e-8, 1e-9, 1e-10, 1e-11, 1e-12, 1e-13, 1e-14, 1e-15 }; + + double res = (double)int_part; + + if(frac_part != 0) + res += (double)frac_part * factor[nfrac]; + + if(neg) + res = -res; + // all done if(result) -#ifdef FP_FAST_FMA - *result = copysign(fma((double)frac, mult[nfrac], (double)val), sign); -#else - *result = copysign(frac * mult[nfrac] + val, sign); -#endif + *result = res; return FE_OK; } @@ -228,14 +279,14 @@ fix_error get_fix_tag_as_boolean(const fix_group* const group, unsigned tag, boo // matchers (unsafe macros!) #define READ_FIRST_DIGIT(s, r) \ - switch(CHAR_TO_INT(*(s))) { \ + switch(*(s)) { \ case '0' ... '9': (r) = *(s) - '0'; break; \ default: RETURN( FE_INCORRECT_VALUE_FORMAT ); \ } \ ++(s) #define READ_DIGIT(s, r) \ - switch(CHAR_TO_INT(*(s))) { \ + switch(*(s)) { \ case '0' ... '9': (r) = (r) * 10 + *(s) - '0'; break; \ default: RETURN( FE_INCORRECT_VALUE_FORMAT ); \ } \ @@ -309,7 +360,7 @@ fix_error read_time_part(const fix_group* const group, fix_string* const ps, utc static fix_error read_time_ms_part(const fix_group* const group, fix_string* const ps, utc_timestamp* const ts) { - fix_error err = read_time_part(group, ps, ts); + const fix_error err = read_time_part(group, ps, ts); if(err != FE_OK) return err; @@ -470,7 +521,7 @@ fix_error get_fix_tag_as_LocalMktDate(const fix_group* const group, unsigned tag // FUNCTION EXPECTS A STRING IN THE "YYYY-MM-DD" FORMAT. fix_string value; - fix_error err = get_fix_tag_as_string(group, tag, &value); + const fix_error err = get_fix_tag_as_string(group, tag, &value); if(err != FE_OK) return err; diff --git a/src/parser.c b/src/parser.c index 9fc94e1..629c8db 100644 --- a/src/parser.c +++ b/src/parser.c @@ -116,9 +116,9 @@ unsigned read_uint(fix_parser* const parser, const char delim) // assuming (s < parser->frame.end) and *(parser->frame.end - 1) == SOH - unsigned tag = CHAR_TO_INT(*s++) - '0'; + unsigned res = CHAR_TO_INT(*s++) - '0'; - if(tag == 0 || tag > 9) // leading zeroes are not allowed + if(res == 0 || res > 9) // leading zeroes are not allowed goto ERROR_EXIT; for(unsigned c = CHAR_TO_INT(*s++); c != CHAR_TO_INT(delim); c = CHAR_TO_INT(*s++)) @@ -128,16 +128,16 @@ unsigned read_uint(fix_parser* const parser, const char delim) if(c > 9) // not a digit goto ERROR_EXIT; - c = tag * 10 + c; + c = res * 10 + c; - if(c < tag) // overflow + if(c < res) // overflow goto ERROR_EXIT; - tag = c; + res = c; } parser->result.error.context.end = parser->frame.begin = s; - return tag; + return res; ERROR_EXIT: parser->result.error.context.end = s; @@ -157,17 +157,14 @@ unsigned next_tag(fix_parser* const parser) } // read tag - unsigned tag = read_uint(parser, '='); + const unsigned tag = parser->result.error.tag = read_uint(parser, '='); if(tag == 0) // invalid tag { parser->result.error.code = FE_INVALID_TAG; - parser->result.error.tag = 0; return 0; } - parser->result.error.tag = tag; - if(parser->frame.begin == parser->frame.end) // empty tag value { parser->result.error.code = FE_EMPTY_VALUE; @@ -206,14 +203,8 @@ unsigned read_uint_value(fix_parser* const parser) { const unsigned val = read_uint(parser, SOH); - if(val != 0) - { - parser->result.error.code = FE_OK; - return val; - } - - parser->result.error.code = FE_INCORRECT_VALUE_FORMAT; - return 0; + parser->result.error.code = (val != 0) ? FE_OK : FE_INCORRECT_VALUE_FORMAT; + return val; } // read bytes to the first SOH, i.e., a FIX string @@ -623,7 +614,7 @@ const fix_error_details* get_fix_group_error_details(const fix_group* const grou } // tag accessors ------------------------------------------------------------------------------------------ -static +static inline fix_error set_group_error(const fix_group* const group, unsigned tag, fix_error err) { set_error_ctx(group->error, err, tag, EMPTY_STR); diff --git a/src/scanner.c b/src/scanner.c index e15a25b..5882c19 100644 --- a/src/scanner.c +++ b/src/scanner.c @@ -102,24 +102,21 @@ static unsigned char copy_cs(char* restrict p, const char* restrict s, unsigned n) { unsigned char cs = 0; + const char* const end = s + n; #ifdef USE_SSE - if(n >= 16) + if(end - s >= 16) { __m128i cs128 = _mm_loadu_si128((const __m128i*)s); - s += 16; _mm_storeu_si128((__m128i*)p, cs128); - p += 16; - while((n -= 16) >= 16) + for(s += 16, p += 16; end - s >= 16; s += 16, p += 16) { const __m128i tmp = _mm_loadu_si128((const __m128i*)s); - s += 16; - _mm_storeu_si128((__m128i*)p, tmp); - p += 16; cs128 = _mm_add_epi8(cs128, tmp); + _mm_storeu_si128((__m128i*)p, tmp); } cs128 = _mm_add_epi8(cs128, _mm_srli_si128(cs128, 8)); @@ -129,22 +126,21 @@ unsigned char copy_cs(char* restrict p, const char* restrict s, unsigned n) cs += _mm_extract_epi16(cs128, 0); // SSE4: _mm_extract_epi8 ? } - if(n >= 8) + if(end - s >= 8) { __m128i cs64 = _mm_loadl_epi64((const __m128i*)s); _mm_storel_epi64((__m128i*)p, cs64); + s += 8; + p += 8; cs64 = _mm_add_epi8(cs64, _mm_srli_si128(cs64, 4)); cs64 = _mm_add_epi8(cs64, _mm_srli_si128(cs64, 2)); cs64 = _mm_add_epi8(cs64, _mm_srli_si128(cs64, 1)); cs += _mm_extract_epi16(cs64, 0); // SSE4: _mm_extract_epi8 ? - p += 8; - s += 8; - n -= 8; } #endif // #ifdef USE_SSE - while(n-- > 0) + while(s < end) cs += (*p++ = *s++); return cs;