Added SIExp for si unit exponents tracking.

pull/645/head^2
Fredrik Öhrström 2022-11-06 12:06:18 +01:00
rodzic 34469fc34e
commit 4624cf8269
3 zmienionych plików z 182 dodań i 0 usunięć

Wyświetl plik

@ -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() != "") { 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:

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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.