kopia lustrzana https://github.com/weetmuts/wmbusmeters
Added SIExp for si unit exponents tracking.
rodzic
34469fc34e
commit
4624cf8269
|
@ -55,6 +55,7 @@ void test_status_join();
|
|||
void test_status_sort();
|
||||
void test_field_matcher();
|
||||
void test_units_extraction();
|
||||
void test_si_units_siexp();
|
||||
void test_si_units_basic();
|
||||
void test_si_units_conversion();
|
||||
void test_formulas_building();
|
||||
|
@ -116,6 +117,7 @@ int main(int argc, char **argv)
|
|||
if (test("status_sort", pattern)) test_status_sort();
|
||||
if (test("field_matcher", pattern)) test_field_matcher();
|
||||
if (test("units_extraction", pattern)) test_units_extraction();
|
||||
if (test("si_units_siexp", pattern)) test_si_units_siexp();
|
||||
if (test("si_units_basic", pattern)) test_si_units_basic();
|
||||
if (test("si_units_conversion", pattern)) test_si_units_conversion();
|
||||
if (test("formulas_building", pattern)) test_formulas_building();
|
||||
|
@ -1682,6 +1684,32 @@ void test_si_convert(double from_value, double expected_value,
|
|||
}
|
||||
}
|
||||
|
||||
void test_si_units_siexp()
|
||||
{
|
||||
// m3/s
|
||||
SIExp e = SIExp::build().s(-1).m(3);
|
||||
if (e.str() != "m³s⁻¹") { printf("ERROR Expected m³s⁻¹ but got \"%s\"\n", e.str().c_str()); }
|
||||
|
||||
SIExp f = SIExp::build().s(1);
|
||||
if (f.str() != "s") { printf("ERROR Expected s but got \"%s\"\n", f.str().c_str()); }
|
||||
|
||||
SIExp g = e.mul(f);
|
||||
if (g.str() != "m³") { printf("ERROR Expected m³ but got \"%s\"\n", g.str().c_str()); }
|
||||
|
||||
SIExp h = SIExp::build().s(127);
|
||||
|
||||
// Test overflow of exponent for seconds!
|
||||
SIExp i = h.mul(f);
|
||||
if (i.str() != "!s⁻¹²⁸-Invalid!") { printf("ERROR Expected !s⁻¹²⁸-Invalid! but got \"%s\"\n", i.str().c_str()); }
|
||||
|
||||
SIExp j = e.div(e);
|
||||
if (j.str() != "1") { printf("ERROR Expected 1 but got \"%s\"\n", j.str().c_str()); }
|
||||
|
||||
SIExp bad = SIExp::build().k(1).c(1);
|
||||
if (bad.str() != "!kc-Invalid!") { printf("ERROR Expected !kc-Invalid! but got \"%s\"\n", bad.str().c_str()); }
|
||||
|
||||
}
|
||||
|
||||
void test_si_units_basic()
|
||||
{
|
||||
// A kilowatt unit generated from scratch:
|
||||
|
|
82
src/units.cc
82
src/units.cc
|
@ -456,3 +456,85 @@ string SIUnit::info()
|
|||
str().c_str(),
|
||||
toString(quantity_));
|
||||
}
|
||||
|
||||
int8_t SIExp::safe_add(int8_t a, int8_t b)
|
||||
{
|
||||
int sum = a+b;
|
||||
if (sum > 127) invalid_ = true;
|
||||
return sum;
|
||||
}
|
||||
|
||||
int8_t SIExp::safe_sub(int8_t a, int8_t b)
|
||||
{
|
||||
int diff = a-b;
|
||||
if (diff < -128) invalid_ = true;
|
||||
return diff;
|
||||
}
|
||||
|
||||
bool SIExp::operator==(const SIExp &e) const
|
||||
{
|
||||
return
|
||||
s_ == e.s_ &&
|
||||
m_ == e.m_ &&
|
||||
kg_ == e.kg_ &&
|
||||
a_ == e.a_ &&
|
||||
mol_ == e.mol_ &&
|
||||
cd_ == e.cd_ &&
|
||||
k_ == e.k_ &&
|
||||
c_ == e.c_ &&
|
||||
f_ == e.f_;
|
||||
}
|
||||
|
||||
SIExp SIExp::mul(const SIExp &e) const
|
||||
{
|
||||
SIExp ee;
|
||||
|
||||
ee .s(ee.safe_add(s(),e.s()))
|
||||
.m(ee.safe_add(m(),e.m()))
|
||||
.kg(ee.safe_add(kg(),e.kg()))
|
||||
.a(ee.safe_add(a(),e.a()))
|
||||
.mol(ee.safe_add(mol(),e.mol()))
|
||||
.cd(ee.safe_add(cd(),e.cd()))
|
||||
.k(ee.safe_add(k(),e.k()))
|
||||
.c(ee.safe_add(c(),e.c()))
|
||||
.f(ee.safe_add(f(),e.f()));
|
||||
|
||||
return ee;
|
||||
}
|
||||
|
||||
SIExp SIExp::div(const SIExp &e) const
|
||||
{
|
||||
SIExp ee;
|
||||
ee .s(ee.safe_sub(s(),e.s()))
|
||||
.m(ee.safe_sub(m(),e.m()))
|
||||
.kg(ee.safe_sub(kg(),e.kg()))
|
||||
.a(ee.safe_sub(a(),e.a()))
|
||||
.mol(ee.safe_sub(mol(),e.mol()))
|
||||
.cd(ee.safe_sub(cd(),e.cd()))
|
||||
.k(ee.safe_sub(k(),e.k()))
|
||||
.c(ee.safe_sub(c(),e.c()))
|
||||
.f(ee.safe_sub(f(),e.f()));
|
||||
|
||||
return ee;
|
||||
}
|
||||
|
||||
#define DO_UNIT_SIEXP(var, name) if (var != 0) { if (r.length()>0) { } r += #name; if (var != 1) { r += to_superscript(var); } }
|
||||
|
||||
string SIExp::str() const
|
||||
{
|
||||
string r;
|
||||
|
||||
DO_UNIT_SIEXP(mol_, mol);
|
||||
DO_UNIT_SIEXP(cd_, cd);
|
||||
DO_UNIT_SIEXP(kg_, kg);
|
||||
DO_UNIT_SIEXP(m_, m);
|
||||
DO_UNIT_SIEXP(k_, k);
|
||||
DO_UNIT_SIEXP(c_, c);
|
||||
DO_UNIT_SIEXP(f_, f);
|
||||
DO_UNIT_SIEXP(s_, s);
|
||||
DO_UNIT_SIEXP(a_, a);
|
||||
|
||||
if (invalid_) r = "!"+r+"-Invalid!";
|
||||
if (r == "") return "1";
|
||||
return r;
|
||||
}
|
||||
|
|
72
src/units.h
72
src/units.h
|
@ -126,6 +126,78 @@ LIST_OF_QUANTITIES
|
|||
Unknown
|
||||
};
|
||||
|
||||
// The SIUnit is used inside formulas and for conversion between units.
|
||||
// Any numeric named Unit can be expressed using an SIUnit.
|
||||
// Most SIUnits do not have a corresponding named Unit.
|
||||
// However the result of a formula calculation or conversion will
|
||||
// be eventually converted into a named unit. So even if you
|
||||
// are allowed to write a formula: 9 kwh * 6 kwh which generates
|
||||
// the unit kwh² which is not explicitly named above,
|
||||
// you cannot assign this value to any calculated field since kwh^2
|
||||
// is not named. However you can do: sqrt(10 kwh * 10 kwh) which
|
||||
// will generated an SIUnit which is equivalent to kwh.
|
||||
//
|
||||
// Other valid formulas are for example:
|
||||
// energy_kwh = 100 kw * 22 h
|
||||
// flow_m3h = 100 m3 / 5 h
|
||||
//
|
||||
// We can only track units raised to the power of 127 or -128.
|
||||
// The struct SIExp below is used to track the exponents of the units.
|
||||
//
|
||||
struct SIExp
|
||||
{
|
||||
SIExp &s(int8_t i) { s_ = i; return *this; }
|
||||
SIExp &m(int8_t i) { m_ = i; return *this; }
|
||||
SIExp &kg(int8_t i) { kg_ = i; return *this; }
|
||||
SIExp &a(int8_t i) { a_ = i; return *this; }
|
||||
SIExp &mol(int8_t i) { mol_ = i; return *this; }
|
||||
SIExp &cd(int8_t i) { cd_ = i; return *this; }
|
||||
SIExp &k(int8_t i) { k_ = i; if (k_ != 0 && (c_ != 0 || f_ != 0)) { invalid_ = true; } return *this; }
|
||||
SIExp &c(int8_t i) { c_ = i; if (c_ != 0 && (k_ != 0 || f_ != 0)) { invalid_ = true; } return *this; }
|
||||
SIExp &f(int8_t i) { f_ = i; if (f_ != 0 && (k_ != 0 || c_ != 0)) { invalid_ = true; } return *this; }
|
||||
|
||||
int8_t s() const { return s_; }
|
||||
int8_t m() const { return m_; }
|
||||
int8_t kg() const { return kg_; }
|
||||
int8_t a() const { return a_; }
|
||||
int8_t mol() const { return mol_; }
|
||||
int8_t cd() const { return cd_; }
|
||||
int8_t k() const { return k_; }
|
||||
int8_t c() const { return c_; }
|
||||
int8_t f() const { return f_; }
|
||||
SIExp mul(const SIExp &e) const;
|
||||
SIExp div(const SIExp &e) const;
|
||||
int8_t safe_add(int8_t a, int8_t b);
|
||||
int8_t safe_sub(int8_t a, int8_t b);
|
||||
bool operator==(const SIExp &e) const;
|
||||
|
||||
std::string str() const;
|
||||
|
||||
static SIExp build() { return SIExp(); }
|
||||
|
||||
private:
|
||||
|
||||
int8_t s_ {};
|
||||
int8_t m_ {};
|
||||
int8_t kg_ {};
|
||||
int8_t a_ {};
|
||||
int8_t mol_ {};
|
||||
int8_t cd_ {};
|
||||
int8_t k_ {};
|
||||
int8_t c_ {}; // Why c and f here? Because they are offset against K.
|
||||
int8_t f_ {}; // Since SIUnits are also used for conversions of values, not just unit tracking,
|
||||
// this means that the offset units (c,f) must be treated as distinct units.
|
||||
// E.g. if you calculated m3*k (and forget the m3 and k value) then you
|
||||
// cannot convert this value into m3*c since the offset makes the calculation
|
||||
// depend on the k value, which we no longer know.
|
||||
// But kw*h can be translated into kw*s since scaling (*3600) can be done on the
|
||||
// calculated value without knowing the h value. Therefore we have to
|
||||
// treat k, c and f as distinct units. I.e. you cannot add m3*k+m3*f+m3*c.
|
||||
|
||||
// If exponents have over/underflowed or if multiple of k,c,f are set, then the SIExp is not valid any more!
|
||||
bool invalid_ = false;
|
||||
};
|
||||
|
||||
struct SIUnit
|
||||
{
|
||||
// Transform a double,double,uint64_t into an SIUnit.
|
||||
|
|
Ładowanie…
Reference in New Issue