From deae0ac01274b339b7bc6dfbf0ab19d3a6c40b60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Offringa?= <offringa@astron.nl> Date: Fri, 16 Mar 2012 20:27:03 +0000 Subject: [PATCH] Task #1892: fixing bug causing strategy reader to malfunction with different locales --- .gitattributes | 3 + .../strategy/control/strategyreader.h | 4 +- .../AOFlagger/test/util/numberparsertest.h | 72 +++++++ .../AOFlagger/test/util/utiltestgroup.h | 37 ++++ .../include/AOFlagger/util/numberparser.h | 188 ++++++++++++++++++ .../src/strategy/control/strategyreader.cpp | 14 +- CEP/DP3/AOFlagger/test/aotest.cpp | 6 + 7 files changed, 318 insertions(+), 6 deletions(-) create mode 100644 CEP/DP3/AOFlagger/include/AOFlagger/test/util/numberparsertest.h create mode 100644 CEP/DP3/AOFlagger/include/AOFlagger/test/util/utiltestgroup.h create mode 100644 CEP/DP3/AOFlagger/include/AOFlagger/util/numberparser.h diff --git a/.gitattributes b/.gitattributes index 955bb687a19..3c6a54f7234 100644 --- a/.gitattributes +++ b/.gitattributes @@ -500,6 +500,8 @@ CEP/DP3/AOFlagger/include/AOFlagger/test/testingtools/lessthanasserter.h -text CEP/DP3/AOFlagger/include/AOFlagger/test/testingtools/testgroup.h -text CEP/DP3/AOFlagger/include/AOFlagger/test/testingtools/testitem.h -text CEP/DP3/AOFlagger/include/AOFlagger/test/testingtools/unittest.h -text +CEP/DP3/AOFlagger/include/AOFlagger/test/util/numberparsertest.h -text +CEP/DP3/AOFlagger/include/AOFlagger/test/util/utiltestgroup.h -text CEP/DP3/AOFlagger/include/AOFlagger/types.h -text CEP/DP3/AOFlagger/include/AOFlagger/util/aologger.h -text CEP/DP3/AOFlagger/include/AOFlagger/util/autoarray.h -text @@ -507,6 +509,7 @@ CEP/DP3/AOFlagger/include/AOFlagger/util/compress.h -text CEP/DP3/AOFlagger/include/AOFlagger/util/ffttools.h -text CEP/DP3/AOFlagger/include/AOFlagger/util/integerdomain.h -text CEP/DP3/AOFlagger/include/AOFlagger/util/multiplot.h -text +CEP/DP3/AOFlagger/include/AOFlagger/util/numberparser.h -text CEP/DP3/AOFlagger/include/AOFlagger/util/parameter.h -text CEP/DP3/AOFlagger/include/AOFlagger/util/plot.h -text CEP/DP3/AOFlagger/include/AOFlagger/util/progresslistener.h -text diff --git a/CEP/DP3/AOFlagger/include/AOFlagger/strategy/control/strategyreader.h b/CEP/DP3/AOFlagger/include/AOFlagger/strategy/control/strategyreader.h index f0944b12bf9..57165808e52 100644 --- a/CEP/DP3/AOFlagger/include/AOFlagger/strategy/control/strategyreader.h +++ b/CEP/DP3/AOFlagger/include/AOFlagger/strategy/control/strategyreader.h @@ -1,6 +1,6 @@ /*************************************************************************** - * Copyright (C) 2008 by A.R. Offringa * - * offringa@astro.rug.nl * + * Copyright (C) 2012 by A.R. Offringa * + * offringa@astro.rug.nl * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * diff --git a/CEP/DP3/AOFlagger/include/AOFlagger/test/util/numberparsertest.h b/CEP/DP3/AOFlagger/include/AOFlagger/test/util/numberparsertest.h new file mode 100644 index 00000000000..6dc17cf96d2 --- /dev/null +++ b/CEP/DP3/AOFlagger/include/AOFlagger/test/util/numberparsertest.h @@ -0,0 +1,72 @@ +/*************************************************************************** + * Copyright (C) 2008 by A.R. Offringa * + * offringa@astro.rug.nl * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef AOFLAGGER_NUMBERPARSERTEST_H +#define AOFLAGGER_NUMBERPARSERTEST_H + +#include <AOFlagger/test/testingtools/asserter.h> +#include <AOFlagger/test/testingtools/unittest.h> + +#include <AOFlagger/util/numberparser.h> + +class NumberParserTest : public UnitTest { + public: + NumberParserTest() : UnitTest("Number parser") + { + AddTest(TestToDouble(), "Parsing double"); + } + + private: + struct TestToDouble : public Asserter + { + void operator()(); + }; +}; + +inline void NumberParserTest::TestToDouble::operator()() +{ + AssertEquals<double>(NumberParser::ToDouble("1"), 1.0); + AssertEquals<double>(NumberParser::ToDouble("1."), 1.0); + AssertEquals<double>(NumberParser::ToDouble("1.000000"), 1.0); + AssertEquals<double>(NumberParser::ToDouble("-1"), -1.0); + AssertEquals<double>(NumberParser::ToDouble("-1.00000"), -1.0); + + AssertEquals<double>(NumberParser::ToDouble("3.14159265"), 3.14159265); + AssertEquals<double>(NumberParser::ToDouble("0.00002"), 0.00002); + AssertEquals<double>(NumberParser::ToDouble("234567"), 234567.0); + AssertEquals<double>(NumberParser::ToDouble("234.567"), 234.567); + + AssertEquals<double>(NumberParser::ToDouble("0"), 0.0); + AssertEquals<double>(NumberParser::ToDouble("0.0"), 0.0); + AssertEquals<double>(NumberParser::ToDouble("-0.0"), 0.0); + + AssertEquals<double>(NumberParser::ToDouble("0.0e5"), 0.0); + AssertEquals<double>(NumberParser::ToDouble("0.0e100"), 0.0); + AssertEquals<double>(NumberParser::ToDouble("0.0e-100"), 0.0); + AssertEquals<double>(NumberParser::ToDouble("0.0E5"), 0.0); + AssertEquals<double>(NumberParser::ToDouble("0.0E100"), 0.0); + AssertEquals<double>(NumberParser::ToDouble("0.0E-100"), 0.0); + + AssertAlmostEqual(NumberParser::ToDouble("1.0e5"), 1.0e5); + AssertAlmostEqual(NumberParser::ToDouble("1.0e-5"), 1.0e-5); + AssertAlmostEqual(NumberParser::ToDouble("0.3e0"), 0.3); + AssertAlmostEqual(NumberParser::ToDouble("642.135e8"), 642.135e8); +} + +#endif diff --git a/CEP/DP3/AOFlagger/include/AOFlagger/test/util/utiltestgroup.h b/CEP/DP3/AOFlagger/include/AOFlagger/test/util/utiltestgroup.h new file mode 100644 index 00000000000..3fad2e5debb --- /dev/null +++ b/CEP/DP3/AOFlagger/include/AOFlagger/test/util/utiltestgroup.h @@ -0,0 +1,37 @@ +/*************************************************************************** + * Copyright (C) 2008 by A.R. Offringa * + * offringa@astro.rug.nl * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef AOFLAGGER_UTILTESTGROUP_H +#define AOFLAGGER_UTILTESTGROUP_H + +#include <AOFlagger/test/testingtools/testgroup.h> + +#include <AOFlagger/test/util/numberparsertest.h> + +class UtilTestGroup : public TestGroup { + public: + UtilTestGroup() : TestGroup("Common utilities") { } + + virtual void Initialize() + { + Add(new NumberParserTest()); + } +}; + +#endif diff --git a/CEP/DP3/AOFlagger/include/AOFlagger/util/numberparser.h b/CEP/DP3/AOFlagger/include/AOFlagger/util/numberparser.h new file mode 100644 index 00000000000..af55efa0ed4 --- /dev/null +++ b/CEP/DP3/AOFlagger/include/AOFlagger/util/numberparser.h @@ -0,0 +1,188 @@ +/*************************************************************************** + * Copyright (C) 2012 by A.R. Offringa * + * offringa@astro.rug.nl * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include <stdexcept> + +#include <cmath> + +class NumberParsingException : public std::runtime_error +{ + public: + explicit NumberParsingException(const std::string &str) + : std::runtime_error(std::string("Failed to parse number: ") + str) + { + } +}; + +/** + * This class implements functions that copy the behaviour of atof and strtod, etc. + * Unfortunately, atof are locale-dependent and e.g. a Dutch machine will interpret + * a decimal point differently. This is undesirable, hence the reimplementation here. + */ +class NumberParser +{ + public: + /** + * @throws NumberParsingException when @c str can not be parsed. + */ + static double ToDouble(const char *str) + { + return toNumber<double>(str); + } + + /** + * @throws NumberParsingException when @c str can not be parsed. + */ + static short ToShort(const char *str) + { + return toSignedInteger<short>(str); + } + private: + /** + * @throws NumberParsingException when @c str can not be parsed. + */ + template<typename T> + static T toNumber(const char *str) + { + if(*str == 0) + throw NumberParsingException("Supplied string is empty"); + + // Read optional sign + bool isNegative; + if(isNegativeSymbol(*str)) { isNegative = true; ++str; } + else if(isPositiveSymbol(*str)) { isNegative = false; ++str; } + else isNegative = false; + + // Read everything before decimal point + if(!isDigit(*str)) + throw NumberParsingException("Number did not start with digits after optional sign symbol"); + T val = (T) ((*str) - '0'); + ++str; + + while(isDigit(*str)) + { + val = val*10.0 + (T) ((*str) - '0'); + ++str; + } + + // Skip decimal point if given + if(isDecimalPoint(*str)) ++str; + + // Read decimals + T currentValue = 0.1; + while(isDigit(*str)) + { + val += currentValue * (T) ((*str) - '0'); + currentValue /= 10.0; + ++str; + } + + // Read optional exponent + if(isExponentSymbol(*str)) + { + ++str; + + try { + short e = ToShort(str); + val = val * intPow10(e); + } catch(NumberParsingException &e) + { + throw NumberParsingException("Could not parse exponent"); + } + } + // If there was an exponent, ToShort has checked for garbage at the end and + // str is not updated, otherwise we still need to check that... + else if(*str != 0) + throw NumberParsingException("The number contains invalid characters after its digits"); + + if(isNegative) + return -val; + else + return val; + } + + template<typename T> + static T toSignedInteger(const char *str) + { + if(*str == 0) + throw NumberParsingException("Supplied string is empty"); + + // Read optional sign + bool isNegative; + if(isNegativeSymbol(*str)) { isNegative = true; ++str; } + else if(isPositiveSymbol(*str)) { isNegative = false; ++str; } + else isNegative = false; + + // Read digits + if(!isDigit(*str)) + throw NumberParsingException("Integer did not start with digits after optional sign symbol"); + T val = (T) ((*str) - '0'); + ++str; + + while(isDigit(*str)) + { + val = val*10 + (T) ((*str) - '0'); + ++str; + } + + // Check if str is completely read. + if(*str == 0) + { + if(isNegative) + return -val; + else + return val; + } + else + throw NumberParsingException("The integer contains invalid characters after its digits"); + } + + static double intPow10(int par) + { + // TODO this can be done a lot faster and more accurate with knowledge that + // par = int. + return pow10((double) par); + } + + static bool isDigit(char c) + { + return c >= '0' && c <= '9'; + } + + static bool isPositiveSymbol(char c) + { + return c == '+'; + } + + static bool isNegativeSymbol(char c) + { + return c == '-'; + } + + static bool isDecimalPoint(char c) + { + return c == '.'; + } + + static bool isExponentSymbol(char c) + { + return c == 'e' || c == 'E'; + } +}; diff --git a/CEP/DP3/AOFlagger/src/strategy/control/strategyreader.cpp b/CEP/DP3/AOFlagger/src/strategy/control/strategyreader.cpp index ab2ce9d70a9..ca82c6f80f4 100644 --- a/CEP/DP3/AOFlagger/src/strategy/control/strategyreader.cpp +++ b/CEP/DP3/AOFlagger/src/strategy/control/strategyreader.cpp @@ -19,6 +19,8 @@ ***************************************************************************/ #include <AOFlagger/strategy/control/strategyreader.h> +#include <AOFlagger/util/numberparser.h> + #include <AOFlagger/strategy/actions/absthresholdaction.h> #include <AOFlagger/strategy/actions/action.h> #include <AOFlagger/strategy/actions/adapter.h> @@ -100,19 +102,23 @@ Strategy *StrategyReader::CreateStrategyFromFile(const std::string &filename) xmlChar *formatVersionCh = xmlGetProp(curNode, BAD_CAST "format-version"); if(formatVersionCh == 0) throw StrategyReaderError("Missing attribute 'format-version'"); - double formatVersion = atof((const char*) formatVersionCh); + double formatVersion = NumberParser::ToDouble((const char*) formatVersionCh); xmlFree(formatVersionCh); xmlChar *readerVersionRequiredCh = xmlGetProp(curNode, BAD_CAST "reader-version-required"); if(readerVersionRequiredCh == 0) throw StrategyReaderError("Missing attribute 'reader-version-required'"); - double readerVersionRequired = atof((const char*) readerVersionRequiredCh); + double readerVersionRequired = NumberParser::ToDouble((const char*) readerVersionRequiredCh); xmlFree(readerVersionRequiredCh); if(readerVersionRequired > STRATEGY_FILE_FORMAT_VERSION) throw StrategyReaderError("This file requires a newer software version"); if(formatVersion < STRATEGY_FILE_FORMAT_VERSION_REQUIRED) - throw StrategyReaderError("This file is too old for the software, please recreate the strategy"); + { + std::stringstream s; + s << "This file is too old for the software, please recreate the strategy. File format version: " << formatVersion << ", oldest version that this software understands: " << STRATEGY_FILE_FORMAT_VERSION_REQUIRED << " (these versions are numbered differently from the software)."; + throw StrategyReaderError(s.str()); + } strategy = parseRootChildren(curNode); } @@ -213,7 +219,7 @@ int StrategyReader::getInt(xmlNode *node, const char *name) const double StrategyReader::getDouble(xmlNode *node, const char *name) const { xmlNode *valNode = getTextNode(node, name); - return atof((const char *) valNode->content); + return NumberParser::ToDouble((const char *) valNode->content); } std::string StrategyReader::getString(xmlNode *node, const char *name) const diff --git a/CEP/DP3/AOFlagger/test/aotest.cpp b/CEP/DP3/AOFlagger/test/aotest.cpp index bfae02d7ded..91b92eef391 100644 --- a/CEP/DP3/AOFlagger/test/aotest.cpp +++ b/CEP/DP3/AOFlagger/test/aotest.cpp @@ -4,6 +4,7 @@ #include <AOFlagger/test/experiments/experimentstestgroup.h> #include <AOFlagger/test/msio/msiotestgroup.h> #include <AOFlagger/test/quality/qualitytestgroup.h> +#include <AOFlagger/test/util/utiltestgroup.h> int main(int argc, char *argv[]) { @@ -24,6 +25,11 @@ int main(int argc, char *argv[]) qualityGroup.Run(); successes += qualityGroup.Successes(); failures += qualityGroup.Failures(); + + UtilTestGroup utilGroup; + utilGroup.Run(); + successes += utilGroup.Successes(); + failures += utilGroup.Failures(); } if(argc > 1 && (std::string(argv[1])=="all" || std::string(argv[1])=="only")) -- GitLab