Skip to content

Instantly share code, notes, and snippets.

@Fiona-J-W
Created June 30, 2014 18:17
Show Gist options
  • Save Fiona-J-W/dfe68b04f0093bea057b to your computer and use it in GitHub Desktop.
Save Fiona-J-W/dfe68b04f0093bea057b to your computer and use it in GitHub Desktop.
#ifndef ENFORCE_H_
#define ENFORCE_H_
#include <cassert>
#include <stdexcept>
#include <string>
namespace Aux {
/**
* Enforces that b is true and throws an Exception otherwise.
*
* If provided, msg must not be null, otherwise the behavior of this
* function is undefined.
*/
template<typename Exception = std::runtime_error>
inline void enforce(bool b, const char* msg = "") {
assert(msg && "Message to enforce must not be nullptr");
if (!b) {
throw Exception{msg};
}
}
/**
* Overload that accepts a std::string. This is mainly for convenience
* while keeping the default-version free of unneeded allocations.
*/
template<typename Exception = std::runtime_error>
inline void enforce(bool b, const std::string& msg) {
enforce<Exception>(b, msg.c_str());
}
/**
* Checks that the provided fstream is opened and throws an exception otherwise.
*/
template<typename Stream>
inline void enforceOpened(const Stream& stream) {
enforce(stream.is_open());
}
/**
* This namespace provides some Types with a static member-function `void enforce(bool)`
* that may check wether the argument is true and create some kind of failure otherwise.
*/
namespace Checkers {
/**
* Checks the bool via assert
*/
struct Asserter {
static void enforce(bool b) {
assert(b);
(void) b; // prevent warnings in release-builds
}
};
/**
* Checks to bool via enforce
*/
struct Enforcer {
static void enforce(bool b) {
::Aux::enforce(b);
}
};
/**
* Calls std::terminate if the bool is false
*/
struct Terminator {
static void enforce(bool b) {
if (!b) {
std::terminate();
}
}
};
/**
* Won't look at the bool (not even in debug-mode, which is how this differs from Asserter)
*/
struct Ignorer{
static void enforce(bool) {}
};
}
} // namespace Aux
#endif // ENFORCE_H_
#include <algorithm>
#include <iostream>
#include <iomanip>
#include "NumberParsing.h"
#include "Enforce.h"
int main() {
using Aux::Parsing::strTo;
auto helper = [](const std::string& str, double expected) {
auto result = std::get<0>(strTo<double>(str.begin(), str.end()));
if (result == expected) {
return;
}
if (std::nexttoward(result, expected) == expected) {
// of by one is ok here
return;
}
std::cerr << std::setprecision(20) << "Expected " << expected << ", actual: " << result << '\n';
};
#define TEST_CASE_REAL(number) helper(#number, number)
TEST_CASE_REAL(0);
TEST_CASE_REAL(1);
TEST_CASE_REAL(8.76464e+23);
TEST_CASE_REAL(5.56628e+12);
TEST_CASE_REAL(9.563574701896000000e+23);
TEST_CASE_REAL(9.563574701896270940e+23);
TEST_CASE_REAL(-669585235219883571019776.);
TEST_CASE_REAL(9.563574701896270946e+23);
TEST_CASE_REAL(141265720771594800000000.);
TEST_CASE_REAL(141265720771594850000000.);
TEST_CASE_REAL(1.4126572077159485e+23);
TEST_CASE_REAL(1.4126572077159480e+23);
TEST_CASE_REAL(-784008854951861199831040.);
TEST_CASE_REAL(-78400885495186119983104.0);
#undef TEST_CASE_REAL
}
# Makefile for a.out
# created with makefile-creator
####################
#Settings:
CXX ?= g++
FLAGS += -O0 -D_GLIBCXX_DEBUG -g -Wall -Wextra -Wpedantic -std=c++1y
LIBS +=
INCLUDES +=
TARGET = a.out
OBJECTS = main.o
####################
#Rules:
$(TARGET) : $(OBJECTS)
$(CXX) $(FLAGS) -o $(TARGET) $(OBJECTS) $(LIBS)
%.o:
$(CXX) $(FLAGS) $(INCLUDES) -c -o $@ $<
clean:
rm *.o
all: $(TARGET)
####################
#Dependencies:
main.o: main.cpp Enforce.h NumberParsing.h makefile
#ifndef NUMBER_PARSING_H
#define NUMBER_PARSING_H
#include <algorithm>
#include <cassert>
#include <tuple>
#include <limits>
#include <stdexcept>
#include <type_traits>
#include "Enforce.h"
namespace Aux {
namespace Parsing {
namespace Impl {
template<typename CharIterator>
std::tuple<CharIterator, char> dropSpaces(CharIterator it, CharIterator end);
template<typename Integer>
double powerOf10(Integer exp);
class IntegerTag{};
template<typename Integer, typename CharIterator, typename ValidationPolicy>
std::tuple<Integer, CharIterator> strTo(CharIterator it, CharIterator end,
IntegerTag);
class RealTag{};
template<typename Real, typename CharIterator, typename ValidationPolicy>
std::tuple<Real, CharIterator> strTo(CharIterator it, CharIterator end, RealTag);
template<typename T>
using ArithmeticTag = typename std::conditional<
std::is_integral<T>::value,
IntegerTag,
typename std::conditional<std::is_floating_point<T>::value,
RealTag, void>::type
>::type;
} // namespace Impl
/**
* Parses a range of characters as number.
*
* @param Number must be either a floating-point-type or an integer-type
* @param CharIterator must be a valid input-iterator over a type that is
* implicitly convertable to char
* @param ValidationPolicy must be a type that is compatible to the checkers from Enforce.h,
* the default is Asserter, which will check conditions via assert()
* @param it the start of the character-range
* @param end the end of the character-range
*
* Requirements: The range [it, end) must contain a valid number.
*
* @returns: A tuple of the parsed value and the iterator after parsing the number and dropping
* any surrounding whitespace.
*
*/
template<typename Number, typename CharIterator, typename ValidationPolicy = Checkers::Asserter>
std::tuple<Number, CharIterator> strTo(CharIterator it, CharIterator end) {
return Impl::strTo<Number, CharIterator, ValidationPolicy>(it, end, Impl::ArithmeticTag<Number>{});
}
namespace Impl {
template<typename Integer, typename CharIterator, typename ValidationPolicy>
std::tuple<Integer, CharIterator> strTo(CharIterator it, const CharIterator end, IntegerTag) {
using Impl::dropSpaces;
ValidationPolicy::enforce(it != end);
char c = *it;
std::tie(it, c) = dropSpaces(it, end);
bool isNegative = false;
if(std::is_signed<Integer>::value) { // this should be optimized away entirely
switch (c) {
case '-':
isNegative = true;
// fallthrough
case '+':
++it;
if (it == end) {
throw std::invalid_argument{
"string contains no digits after sign"};
}
c = *it;
break;
default:
break;
}
}
if(!isdigit(c)) {
throw std::invalid_argument{"string contains no digits"};
}
Integer val = 0;
while (true) {
ValidationPolicy::enforce(std::numeric_limits<Integer>::max() / 10 >= val);
val *= 10;
c -= '0';
ValidationPolicy::enforce(std::numeric_limits<Integer>::max() - c >= val);
val += c;
++it;
if(it == end) {
break;
}
c = *it;
if(!isdigit(c)) {
break;
}
}
if(isNegative) {
val = -val;
}
std::tie(it, c) = dropSpaces(it, end);
return std::make_tuple(val, it);
}
template<typename Real, typename CharIterator, typename ValidationPolicy>
std::tuple<Real, CharIterator> strTo(CharIterator it, const CharIterator end, RealTag) {
static_assert(std::numeric_limits<Real>::max_digits10
<= std::numeric_limits<std::uintmax_t>::digits10,
"can't safe mantissa in biggest integer");
using Impl::dropSpaces;
using Impl::powerOf10;
char c;
bool isNegative = false;
std::uintmax_t mantissa = 0;
int exp = 0;
auto makeReturnValue = [&]() {
Real fp_mantissa = mantissa;
Real value = fp_mantissa * powerOf10(exp);
if (isNegative) {
value = -value;
}
return std::make_tuple(value, it);
};
// drop whitespace:
std::tie(it, c) = dropSpaces(it, end);
ValidationPolicy::enforce(it != end);
// set sign:
switch (c) {
case '-':
isNegative = true;
// fallthrough
case '+':
++it;
if (it == end) {
throw std::invalid_argument{"string contains no digits"};
}
c = *it;
break;
default:
break;
}
// number of decimal digits that can be stored in the mantissa and the used integer
unsigned remainingDigits = std::numeric_limits<Real>::max_digits10;
//read 'big' part of the mantissa:
while (remainingDigits > 0) {
if (!isdigit(c)) {
break;
}
--remainingDigits;
mantissa *= 10;
mantissa += c - '0';
++it;
if (it == end) {
return makeReturnValue();
}
c = *it;
}
if(remainingDigits == 0) {
if(isdigit(c)) {
//round correctly:
if (c - '0' >= 5) {
++mantissa;
}
}
while (isdigit(c)) {
++exp;
++it;
if (it == end) {
break;
}
c = *it;
}
}
// read 'small' part of the mantissa
if (c == '.') {
++it;
if(it == end) {
return makeReturnValue();
}
c = *it;
while (remainingDigits > 0) {
if (!isdigit(c)) {
break;
}
--exp;
--remainingDigits;
mantissa *= 10;
mantissa += c - '0';
++it;
if (it == end) {
return makeReturnValue();
}
c = *it;
}
if (isdigit(c)) {
//round correctly:
if (c - '0' >= 5) {
++mantissa;
}
}
// skip the remaining digits:
it = std::find_if_not(it, end, isdigit);
if (it == end) {
return makeReturnValue();
}
c = *it;
}
// calculate the final exponent:
if (c == 'e' || c == 'E') {
++it;
int tmp;
// we need to pass IntegerTag explicitly here because we are in a
// nested namespace. This shouldn't be required anywhere else
std::tie(tmp, it) = strTo<int, CharIterator, ValidationPolicy>(it, end, IntegerTag{});
exp += tmp;
}
// drop further whitespace:
std::tie(it, c) = dropSpaces(it, end);
return makeReturnValue();
}
template<typename CharIterator>
std::tuple<CharIterator, char> dropSpaces(CharIterator it, CharIterator end) {
char c = 0;
while(it != end && std::isspace((c = *it))) {
++it;
}
return std::make_tuple(it, c);
}
template<typename Integer>
double powerOf10(Integer exp) {
if (exp < 0) {
return 1.0 / powerOf10(-exp);
}
switch(exp) {
case 0: return 1;
case 1: return 10;
case 2: return 100;
case 3: return 1000;
case 4: return 10000;
case 5: return 100000;
case 6: return 1000000;
case 7: return 10000000;
case 8: return 100000000;
case 9: return 1000000000;
default:
auto tmp = powerOf10(exp / 2);
if (exp % 2 == 0) {
return tmp * tmp;
} else {
return tmp * tmp * 10;
}
}
}
} // namespace Impl
}} // namespace Aux::Parsing
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment