The code makes following assumptions:
std::string_viewis not null-terminated- there are no exponents, all floating point inputs are in the way 123.123
- limiting digits before and after decimal point
<ctype.h>free. No locale. Uses ownisspace()andisdigits()
#include <cstdint>
#include <string_view>
#include <limits>
// based on http://www.jbox.dk/sanos/source/lib/strtod.c.html
namespace to_fp_impl_{
namespace{
constexpr char decimal_point = '.';
auto isspace(char c){
return c == ' ' || c == '\t';
};
auto isdigit(char c){
return c >= '0' && c <= '9';
}
template<typename FP>
void add_char(FP &number, char c){
number = number * 10 + (c - '0');
}
}
template<typename FP>
struct ResultFP{
FP num;
bool ok = true;
static auto err(){
return ResultFP{ 0, false };
}
};
} // namespace to_fp_impl_
template<
typename FP,
uint8_t digits = std::numeric_limits<FP>::digits10,
uint8_t decimals = digits
>
auto to_fp(std::string_view const str){
static_assert(
std::is_floating_point_v<FP> &&
digits <= std::numeric_limits<FP>::digits10 &&
decimals <= std::numeric_limits<FP>::digits10
);
// -----
using namespace to_fp_impl_;
using Result = ResultFP<FP>;
auto it = std::begin(str);
auto end = std::end(str);
// -----
// Skip leading whitespace
while (it != end && isspace(*it))
++it;
// -----
// Handle optional sign
int8_t sign = +1;
if (it != end){
if (*it == '-'){
sign = -1;
++it;
}else if (*it == '+'){
// sign = +1;
++it;
}
}
// -----
FP number = 0.0;
uint8_t total_digits = 0;
// Process digits
{
uint8_t num_digits = 0;
while (it != end && isdigit(*it)){
if (++num_digits > digits)
return Result::err();
add_char(number, *it);
++it;
}
total_digits += num_digits;
}
// -----
// Process decimal part
if (it != end && *it == decimal_point){
++it;
FP exp = 1;
uint8_t num_digits = 0;
while (it != end && isdigit(*it)){
if (++num_digits > decimals)
return Result::err();
add_char(number, *it);
++it;
exp *= 0.1;
}
total_digits += num_digits;
number *= exp;
}
// -----
return Result{ number * sign, total_digits != 0 };
}
template<
typename FP,
uint8_t digits = std::numeric_limits<FP>::digits10,
uint8_t decimals = digits
>
auto to_fp_def(std::string_view const str, FP def = 0){
auto[num, ok] = to_fp<FP, digits, decimals>(str);
return ok ? num : def;
}
template<
uint8_t digits = std::numeric_limits<double>::digits10,
uint8_t decimals = digits
>
auto to_double_def(std::string_view const str, double def = 0){
return to_fp_def<double, digits, decimals>(str, def);
}
template<
uint8_t digits = std::numeric_limits<float>::digits10,
uint8_t decimals = digits
>
auto to_float_def(std::string_view const str, float def = 0){
return to_fp_def<float, digits, decimals>(str, def);
}
#include <cstdio>
int main(){
auto _ = [](const char *s){
const char *mask = ">%32.15f<\n";
constexpr double def = -0.111111111111111;
printf(mask, to_double_def(s, def));
};
_(" ." ); //error
_(" -." ); //error
_(" +." ); //error
_(" .0" );
_(" -.0" );
_(" +.0" );
_(" 0." );
_(" -0." );
_(" +0." );
_(" 0" );
_(" -0" );
_(" +0" );
_(" 100.1" );
_("-100.1" );
_("+100.1" );
_(" 123456789012345" );
_(" 123456789012345." );
_(" .123456789012345" );
_(" 123456789012345.123456789012345" ); // OK, but truncated
_(" -123456789012345" );
_(" -123456789012345." );
_(" -.123456789012345" );
_(" -123456789012345.123456789012345" ); // OK, but truncated
_(" 1234567890123456." ); //error, too long
_(" .1234567890123456" ); //error, too long
_(" 1234567890123456.1234567890123456" ); //error, too long
}
Result:
> -0.111111111111111<
> -0.111111111111111<
> -0.111111111111111<
> 0.000000000000000<
> -0.000000000000000<
> 0.000000000000000<
> 0.000000000000000<
> -0.000000000000000<
> 0.000000000000000<
> 0.000000000000000<
> -0.000000000000000<
> 0.000000000000000<
> 100.100000000000009<
> -100.100000000000009<
> 100.100000000000009<
> 123456789012345.000000000000000<
> 123456789012345.000000000000000<
> 0.123456789012345<
> 123456789012345.250000000000000<
>-123456789012345.000000000000000<
>-123456789012345.000000000000000<
> -0.123456789012345<
>-123456789012345.250000000000000<
> -0.111111111111111<
> -0.111111111111111<
> -0.111111111111111<
Benchmark:
the code is 4.6 x faster than std::strtod(), but slower than std::from_chars()
https://quick-bench.com/q/cHVL5PW9m4WSp6OvPnrF-ApCrG4
std::strtod()andstd::from_chars()? \$\endgroup\$std::strtod(), but slower thanstd::from_chars()quick-bench.com/q/cHVL5PW9m4WSp6OvPnrF-ApCrG4 \$\endgroup\$