I\'ve a funny problem with school exercise. I am given latitude and longitude and I have to ensure that it is in right format: \\(\\d+\\.\\d+[NS], \\d\\+.\\d+[EW]\\)
You can replace E
with X
(for example) in the input string, after running your code you can replace back X
with E
in EW
variable.
char lBracket, rBracket, comma, NS, EW;
int nLat, nLon;
double lat, lon;
std::string inputString = "(51.5N, 0.0E)";
size_t e_pos = inputString.find( 'E' );
if ( e_pos != std::string::npos ) {
inputString.replace( e_pos, e_pos + 1, 'X' );
}
istringstream iss ( inputString );
iss >> fixed >> lBracket >> nLat >> lat >> NS >> comma >>
nLon >> lon >> EW >> rBracket;
if ( EW == 'X' ) {
EW = 'E';
}
lat += nLat;
lon += nLon;
Update: Sorry, didn't see your remark
Only solution I came up with is replace the E with some other letter, which would probably work, but isn't really nice piece of code.
If you want - I'll delete an answer
Your problem is not what you think. .0E
is not a valid floating point numeral
even in scientific notation. It lacks any exponent.
What is happening is that the stream parser is commited to the interpretation
of .0E
as a scientfic floating point numeral by the time it reaches the E
, then
finds no exponent; concludes that its interpretation is falsified; assigns 0 to
the target double and set the failbit
in iss
, so no further extraction from the
stream is possible. You could verify this by changing 0.0E
to 1.1E
and
testing iss.fail()
right after the attempted extraction of lon.
You would
still find that lon
is set to 0, not 0.1, and that iss.fail() == true
.
I believe there is no way to avoid this in the context w.fE
(w
= whole part, f
= fractional part)
if you attempt to extract either the w.f
or just the .f
into a floating point variable with >>
.
You are out of the comfort zone of formatted >>
extraction in this corner case
and will need to get fiddly. And in fact that need does not spring solely from
the w.fE
corner case, given the pattern \(\d+\.\d+[NS], \d\+.\d+[EW]\)
that
you tell us the references must satisfy. The \d+\.\d+[NS]
part is also too
exacting for >> double >> char
or for >> [unsigned|int] >> double >> char
:
the integral or floating point extractors would consume a leading +
or -
and the floating point extractors would not insist on the presence of a
decimal point or on non-zero counts of digits both before and after it.
The fiddliness of parsing your map references (without aid of regexes) would
prompt me to make a map_ref
class for them, such that you can try to extract a
map_ref
from an input stream (probably also from a string); can ask a map_ref
whether it is a good or bad one (e.g. after an attempted extraction), and can insert
a formatted map_ref
into an output stream.
Here is a sketch for such a class:
#include <ostream>
#include <istream>
#include <iomanip>
#include <utility>
#include <limits>
struct map_ref
{
map_ref() = default;
map_ref(char NS, double lat, char EW, double lon)
: _NS(NS == 'N' || NS == 'S' ? NS : '?'),
_EW(EW == 'E' || EW == 'W' ? EW : '?'),
_lat(lat >= 0.0 && lat <= 90.0 ? lat : -1),
_lon(lon >= 0.0 && lon <= 180.0 ? lon : -1),
_good(_NS != '?' && _EW != '?' && _lat != -1 && _lon != -1){}
// Todo: string ctor
std::pair<char,double> latitude() const {
return std::make_pair(_NS,_lat);
}
std::pair<char,double> longitude() const {
return std::make_pair(_EW,_lon);
}
// Todo: setters, getters, equality etc.
bool good() const {
return _good;
}
bool read(std::istream & in);
std::ostream & write(std::ostream & out, std::size_t precision = 4) const {
return out << std::fixed << std::setprecision(precision) <<
'(' << _lat << _NS << ", " << _lon << _EW << ')';
}
void clear() {
*this = map_ref();
}
private:
double
read_fixed_point(std::istream & in, char & dest, std::string const & delims);
char _NS = '?';
char _EW = '?';
double _lat = -1;
double _lon = -1;
bool _good = false;
};
double
map_ref::read_fixed_point(
std::istream & in, char & dest, std::string const & delims)
{
std::string s;
unsigned whole_digs = 0, frac_digs = 0;
while(in >> dest &&
dest != '.' && delims.find(dest) == std::string::npos)
{
whole_digs += std::isdigit(dest) != 0;
s += dest;
}
if (dest != '.') {
return -1;
}
s += dest;
while(in >> dest && delims.find(dest) == std::string::npos)
{
frac_digs += std::isdigit(dest) != 0;
s += dest;
}
if (whole_digs == 0 || frac_digs == 0 ||
whole_digs + frac_digs > std::numeric_limits<double>::digits10 ||
s.length() != 1 + whole_digs + frac_digs) {
return -1;
}
return std::stod(s);
}
bool map_ref::read(std::istream & in)
{
char lparen = 0;
clear();
in >> std::noskipws >> lparen;
if (lparen != '(') {
return _good = false;
}
_lat = read_fixed_point(in,_NS,"NS");
if (_lat < 0.0 || _lat > 90.0) {
return _good = false;
}
char comma = 0;
in >> comma;
if (comma != ',') {
return _good = false;
}
while (std::isspace(in.peek())) {
_EW = in.get();
}
_lon = read_fixed_point(in,_EW,"EW");
if (_lon < 0.0 || _lon > 180.0) {
return _good = false;
}
char rparen = 0;
in >> rparen;
return _good = rparen == ')';
}
std::istream & operator>>(std::istream & in, map_ref & mr)
{
mr.read(in);
return in;
}
std::ostream & operator<<(std::ostream & out, map_ref const & mr)
{
return mr.write(out);
}
The class incorporates the obvious constraints that the latitude in a good
map_ref
shall be <= 90 North or South, and that the longitude shall be <= 180 East or West.
The read(std::istream &)
method parses map references as per your pattern,
with the slight relaxation that 0 or more spaces are acceptable after the comma.
Note that it classifies a parsed map reference as bad if either the
latitude or longitude contains more digits than can be respresented without change
in a double
(i.e. more than std::numeric_limits<double>::digits10
== 15)
The method:
std::ostream & write(std::ostream & out, std::size_t precision = 4)
lets you specify the precision with which the latitude and longitude will
be represented in the output stream, as per std::set_precision(precision) or
accept the default 4
. If you set it to 0, then you'll lose all decimal places
on output, so don't do that.
The class comes with overloads of the global operators >>
and <<
for
formatted extraction and insertion of map_ref
s. They delegate
respectively to map_ref::read
and map_ref::write
, with the default precision
in the latter case; so for any other output precision, invoke map_ref::write
directly.
For some tests of map_ref
parsing, you can append the following:
#include <iostream>
#include <sstream>
static unsigned tests = 0, pass = 0, fail = 0;
static void expect_good(char NS, double lat, char EW, double lon )
{
std::cout << "Testing (" << ++tests << ") "
<< NS << ',' << lat << ',' << EW << ',' << lon << '\n';
map_ref mr(NS,lat,EW,lon);
if (!mr.good()) {
std::cerr << "Failed (" << tests << "): Is good, got bad\n";
++fail;
} else {
++pass;
std::cout << "Passed (" << tests << "): Is good. Got \"" << mr << "\"\n";
}
}
static void expect_bad(char NS, double lat, char EW, double lon )
{
std::cout << "Testing (" << ++tests << ") "
<< NS << ',' << lat << ',' << EW << ',' << lon << '\n';
map_ref mr(NS,lat,EW,lon);
if (mr.good()) {
std::cerr << "Failed (" << tests << "): Is bad, got good\n";
++fail;
} else {
++pass;
std::cout << "Passed (" << tests << "): Is bad, got bad\n";
}
}
static void expect_good(std::string const & s)
{
std::cout << "Testing (" << ++tests << ") \"" << s << "\"\n";
std::istringstream iss(s);
map_ref mr;
iss >> mr;
if (!mr.good()) {
std::cerr << "Failed (" << tests << "): Is good, got bad\n";
++fail;
} else {
++pass;
std::cout << "Passed (" << tests << "): Is good. Got \"" << mr << "\"\n";
}
}
static void expect_bad(std::string const & s)
{
std::cout << "Testing (" << ++tests << ") \"" << s << "\"\n";
std::istringstream iss(s);
map_ref mr;
iss >> mr;
if (mr.good()) {
++fail;
std::cerr << "Failed (" << tests << "): Is bad, got good\n";
} else {
++pass;
std::cout << "Passed (" << tests << "): Is bad, got bad\n";
}
}
int main()
{
expect_bad('E',1.00,'S',1.00);
expect_bad('N',-1.00,'W',1.00);
expect_bad('N',90.00,'W',180.01);
expect_bad('S',90.01,'W',180.00);
expect_good('S',90.00,'E',180.00);
expect_good('S',0.0,'E',0.0);
expect_bad("");
expect_bad("1.1N, 2.2W");
expect_bad("(1.1N, 2.2W");
expect_bad("1.1N, 2.2W)");
expect_bad("[1.1N, 2.2W)");
expect_bad("(1.1N, 2.2W(");
expect_bad("(N)");
expect_bad("(N, W)");
expect_bad("(0N, 1W)");
expect_bad("(1.0N, 2W)");
expect_bad("(1.0N, .2W)");
expect_bad("(.01N, 1.2E)");
expect_bad("(1.N, 1.2W)");
expect_bad("(N1.1, E1.2)");
expect_bad("(1.0N, 1.2 W)");
expect_bad("(1.0X, 1.2W)");
expect_bad("(1.0N, 1.2Z)");
expect_bad("(+1.0N, 1.2E)");
expect_bad("(1.+0N, 1.2E)");
expect_bad("(1.0N, -1.2E)");
expect_bad("(1.0N, 1.-2E)");
expect_bad("(1.1N, 2.3.4E)");
expect_bad("(0.0NN, 0.0E)");
expect_bad("(0.0N, 0.0EW)");
expect_bad("(0.0 1N, 0.0E)");
expect_bad("(0.01N, 0 2.0E)");
expect_bad("(0 .01N, 2.0E)");
expect_bad("(0.01N, 2. 0E)");
expect_bad("(12.34567890123456N, 2.0E)");
expect_good("(0.0N, 0.0E)");
expect_good("(1.0N,1.2W)");
expect_good("(01.0N,01.2W)");
expect_good("(1.0N, 1.2W)");
expect_good("(0.123456789N, 0.000000001E)");
expect_good("(0.000000001S, 0.123456789W)");
expect_good("(0.123456789N, 0.000000001W)");
expect_good("(0.000000001S, 0.123456789E)");
expect_good("(1.1N, 12.3456789012345E)");
expect_bad("(0.1E, 0.1N)");
expect_bad("(0.1W, 0.1S)");
expect_bad("(0.1W, 0.1N)");
expect_bad("(0.1E, 0.1S)");
expect_good("(90.0N, 180.0E)");
expect_good("(90.0S, 180.0W)");
expect_good("(90.0N, 180.0W)");
expect_good("(90.0S, 180.0E)");
expect_bad("(90.000001N, 180.0E)");
expect_bad("(90.000001S, 180.0W)");
expect_bad("(90.0N, 180.000001W)");
expect_bad("(90.0S, 180.000001E)");
std::cout << "Tests: " << tests << std::endl;
std::cout << "Passed: " << pass << std::endl;
std::cout << "Failed: " << fail << std::endl;
return 0;
}
(gcc 4.9.2/clang 3.6, -std=c++11)
You're essentially looking at a parse problem. operator>>(istream&, string&)
is a very simplistic parser that just tokenizes on whitespace.
If your format specification is strict enough, and you should reject (51.5N , 0.0E)
(extra space before comma) then just don't extract the comma. Instead, directly after extraction you should check that nLat
contains the trailing comma and remove it. You no longer need >>comma
.
If you must support optional whitespace anywhere, it may help to preprocess the string by inserting an extra space before and after the comma (unconditionally). If there already was a space, there will now be two. That's no problem as whitespace skipping will skip any amount.