From 081b38a5f11d0eb093fdf3315051e9c1de7135fd Mon Sep 17 00:00:00 2001 From: Sam Moore Date: Tue, 16 Sep 2014 17:26:46 +0800 Subject: [PATCH] ParanoidNumbers work except for the simplifying bit... I really really hope they work anyway. Tester doesn't fail too disastrously, but PN gives a worse result than doubles. --- src/Makefile | 2 +- src/paranoidnumber.cpp | 431 +++++++++++++++++-------------- src/paranoidnumber.h | 130 ++++------ src/tests/paranoidcalculator.cpp | 10 +- src/tests/paranoidtester.cpp | 208 ++++++++++++++- 5 files changed, 488 insertions(+), 293 deletions(-) diff --git a/src/Makefile b/src/Makefile index 2b5fc73..8813b08 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,7 +1,7 @@ #Makefile ARCH := $(shell uname -m) # TODO: stb_truetype doesn't compile with some of these warnings. -CXX = g++ -std=gnu++0x -g -Wall -Werror -Wshadow -pedantic -rdynamic +CXX = g++ -std=c++11 -g -Wall -Werror -Wshadow -pedantic -rdynamic MAIN = main.o OBJ = log.o real.o bezier.o document.o objectrenderer.o view.o screen.o graphicsbuffer.o framebuffer.o shaderprogram.o stb_truetype.o gl_core44.o path.o paranoidnumber.o diff --git a/src/paranoidnumber.cpp b/src/paranoidnumber.cpp index 49cceaf..4769c46 100644 --- a/src/paranoidnumber.cpp +++ b/src/paranoidnumber.cpp @@ -16,7 +16,10 @@ ParanoidNumber::~ParanoidNumber() { g_count--; for (int i = 0; i < NOP; ++i) - delete m_next[i]; + { + for (auto n : m_next[i]) + delete n; + } } ParanoidNumber::ParanoidNumber(const char * str) : m_value(0) @@ -42,12 +45,9 @@ ParanoidNumber::ParanoidNumber(const char * str) : m_value(0) ParanoidNumber n(1); for (int i = dp+1; i < end; ++i) { - Debug("{%s} /= 10", n.Str().c_str()); n/=10; - Debug("{%s}", n.Str().c_str()); ParanoidNumber b(str[i]-'0'); b*=n; - Debug("{%s} += {%s}", Str().c_str(), b.Str().c_str()); this->operator+=(b); } } @@ -57,22 +57,16 @@ ParanoidNumber & ParanoidNumber::operator=(const ParanoidNumber & a) m_value = a.m_value; for (int i = 0; i < NOP; ++i) { - if (a.m_next[i] == NULL) - { - if (m_next[i] != NULL) - delete m_next[i]; - m_next[i] = NULL; - continue; - } - - if (m_next[i] != NULL) + for (unsigned j = 0; j < m_next[i].size() && j < a.m_next[i].size(); ++j) { - m_next[i]->operator=(*(a.m_next[i])); + m_next[i][j]->operator=(*(a.m_next[i][j])); } - else + + for (unsigned j = a.m_next[i].size(); j < m_next[i].size(); ++j) { - m_next[i] = new ParanoidNumber(*(a.m_next[i])); + delete m_next[i][j]; } + m_next[i].resize(a.m_next[i].size()); } return *this; } @@ -84,38 +78,38 @@ string ParanoidNumber::Str() const stringstream s; s << (double)m_value; result += s.str(); - if (m_next[MULTIPLY] != NULL) + for (auto mul : m_next[MULTIPLY]) { result += "*"; - if (m_next[MULTIPLY]->m_next[ADD] != NULL || m_next[MULTIPLY]->m_next[SUBTRACT] != NULL) - result += "(" + m_next[MULTIPLY]->Str() + ")"; + if (!mul->Floating()) + result += "(" + mul->Str() + ")"; else - result += m_next[MULTIPLY]->Str(); + result += mul->Str(); } - if (m_next[DIVIDE] != NULL) + for (auto div : m_next[DIVIDE]) { result += "/"; - if (m_next[DIVIDE]->m_next[ADD] != NULL || m_next[DIVIDE]->m_next[SUBTRACT] != NULL) - result += "(" + m_next[DIVIDE]->Str() + ")"; + if (!div->Floating()) + result += "(" + div->Str() + ")"; else - result += m_next[DIVIDE]->Str(); + result += div->Str(); } - if (m_next[ADD] != NULL) + for (auto add : m_next[ADD]) { result += "+"; - if (m_next[ADD]->m_next[MULTIPLY] != NULL || m_next[ADD]->m_next[DIVIDE] != NULL) - result += "(" + m_next[ADD]->Str() + ")"; + if (!add->Floating()) + result += "(" + add->Str() + ")"; else - result += m_next[ADD]->Str(); + result += add->Str(); } - if (m_next[SUBTRACT] != NULL) + for (auto sub : m_next[SUBTRACT]) { result += "-"; - if (m_next[SUBTRACT]->m_next[MULTIPLY] != NULL || m_next[SUBTRACT]->m_next[DIVIDE] != NULL) - result += "(" + m_next[SUBTRACT]->Str() + ")"; + if (!sub->Floating()) + result += "(" + sub->Str() + ")"; else - result += m_next[SUBTRACT]->Str(); + result += sub->Str(); } @@ -227,217 +221,270 @@ ParanoidNumber & ParanoidNumber::operator/=(const ParanoidNumber & a) return *this; } -/** - * Performs the operation on a with argument b (a += b, a -= b, a *= b, a /= b) - * @returns b if b can safely be deleted - * @returns NULL if b has been merged with a - * append indicates that b should be merged - */ -ParanoidNumber * ParanoidNumber::Operation(ParanoidNumber * b, Optype op, ParanoidNumber ** parent) +// a + b +ParanoidNumber * ParanoidNumber::OperationTerm(ParanoidNumber * b, Optype op, ParanoidNumber ** merge_point, Optype * merge_op) { - if (b == NULL) - return NULL; - - Optype invop = InverseOp(op); // inverse of p - ParanoidNumber * append_at = this; - - if (Floating()) + + if (Floating() && m_value == 0) // 0 + b = b { - if ((op == ADD || op == SUBTRACT) && (m_value == 0)) + m_value = b->m_value; + if (op == SUBTRACT) { - m_value = b->m_value; - for (int i = 0; i < NOP; ++i) - { - m_next[i] = b->m_next[i]; - b->m_next[i] = NULL; - } - return b; + m_value = -m_value; + swap(b->m_next[ADD], b->m_next[SUBTRACT]); } - if ((op == MULTIPLY) && (m_value == 1)) + + for (int i = 0; i < NOP; ++i) { - m_value = b->m_value; - for (int i = 0; i < NOP; ++i) + m_next[i] = b->m_next[i]; + b->m_next[i].clear(); + } + return b; + } + if (b->Floating() && b->m_value == 0) // a + 0 = a + return b; + + + + if (NoFactors() && b->NoFactors()) + { + if (ParanoidOp(m_value, b->m_value, op)) + { + Optype addop = (op == ADD) ? ADD : SUBTRACT; + for (auto add : b->m_next[ADD]) { - m_next[i] = b->m_next[i]; - b->m_next[i] = NULL; + delete OperationTerm(add, addop); } - return b; + Optype subop = (op == ADD) ? SUBTRACT : ADD; + for (auto sub : b->m_next[SUBTRACT]) + delete OperationTerm(sub, subop); + + b->m_next[ADD].clear(); + b->m_next[SUBTRACT].clear(); return b; } + } + + + + + bool parent = (merge_point == NULL); + ParanoidNumber * merge = this; + Optype mop = op; + assert(mop != NOP); // silence compiler warning + if (parent) + { + merge_point = &merge; + merge_op = &mop; + } + else + { + merge = *merge_point; + mop = *merge_op; + } + Optype invop = InverseOp(op); // inverse of p + Optype fwd = op; + Optype rev = invop; + if (op == SUBTRACT) + { + fwd = ADD; + rev = SUBTRACT; } - if (b->Floating()) + for (auto prev : m_next[invop]) { - if ((op == ADD || op == SUBTRACT) && (b->m_value == 0)) + if (prev->OperationTerm(b, rev, merge_point, merge_op) == b) return b; - if ((op == MULTIPLY || op == DIVIDE) && (b->m_value == 1)) + + } + for (auto next : m_next[op]) + { + if (next->OperationTerm(b, fwd, merge_point, merge_op) == b) return b; } - // Operation can be applied directly to the m_value of this and b - // ie: op is + or - and this and b have no * or / children - // or: op is * or / and this and b have no + or - children - if (Pure(op) && (b->Pure(op))) - { - if (ParanoidOp(m_value, b->m_value, op)) // op applied successfully... - { - Simplify(op); - Simplify(invop); - for (int i = 0; i < NOP; ++i) // Try applying b's children to this - { - delete Operation(b->m_next[i], Optype(i)); - b->m_next[i] = NULL; - } - return b; // can delete b + + + + if (parent) + { + merge->m_next[*merge_op].push_back(b); + } + else + { + if (m_next[op].size() == 0) + { + *merge_point = this; + *merge_op = op; } } + return NULL; +} + +ParanoidNumber * ParanoidNumber::OperationFactor(ParanoidNumber * b, Optype op, ParanoidNumber ** merge_point, Optype * merge_op) +{ - // Try to simplify the cases: - // a + b*c == (a/c + b)*c - // a + b/c == (a*c + b)/c - else if ((op == ADD || op == SUBTRACT) && - (Pure(op) || b->Pure(op))) + if (Floating() && m_value == 0) { - - Debug("Simplify: {%s} %c {%s}", Str().c_str(), OpChar(op), b->Str().c_str()); - Optype adj[] = {MULTIPLY, DIVIDE}; - for (int i = 0; i < 2; ++i) + return b; + } + + if (Floating() && m_value == 1 && op == MULTIPLY) + { + m_value = b->m_value; + for (int i = 0; i < NOP; ++i) { - - Optype f = adj[i]; - Optype invf = InverseOp(f); - - Debug("Try %c", OpChar(f)); - - if (m_next[f] == NULL && b->m_next[f] == NULL) - continue; - - ParanoidNumber * tmp_a = new ParanoidNumber(*this); - ParanoidNumber * tmp_b = new ParanoidNumber(*b); - + for (auto n : m_next[i]) + delete n; + m_next[i].clear(); + swap(m_next[i], b->m_next[i]); + } + return b; + } + if (b->Floating() && b->m_value == 1) + return b; - ParanoidNumber * af = (tmp_a->m_next[f] != NULL) ? new ParanoidNumber(*(tmp_a->m_next[f])) : NULL; - ParanoidNumber * bf = (tmp_b->m_next[f] != NULL) ? new ParanoidNumber(*(tmp_b->m_next[f])) : NULL; - - Debug("{%s} %c {%s}", tmp_a->Str().c_str(), OpChar(op), tmp_b->Str().c_str()); - Debug("{%s} %c {%s}", tmp_a->Str().c_str(), OpChar(op), tmp_b->Str().c_str()); - if (tmp_a->Operation(af, invf) != af || tmp_b->Operation(bf, invf) != bf) + if (NoTerms() && b->NoTerms()) + { + if (ParanoidOp(m_value, b->m_value, op)) + { + Optype mulop = (op == MULTIPLY) ? MULTIPLY : DIVIDE; + for (auto mul : b->m_next[MULTIPLY]) { - delete af; - delete bf; - delete tmp_a; - delete tmp_b; - continue; - } - Debug("{%s} %c {%s}", tmp_a->Str().c_str(), OpChar(op), tmp_b->Str().c_str()); - - if (tmp_a->Operation(bf, invf) == bf && tmp_b->Operation(af, invf) == af) // a / c simplifies - { - if (tmp_a->Operation(tmp_b, op) != NULL) // (a/c) + b simplifies - { - this->operator=(*tmp_a); - if (bf != NULL) - delete Operation(bf, f); - if (af != NULL) - delete Operation(af, f); - delete tmp_a; - delete tmp_b; - return b; // It simplified after all! - } - else - { - tmp_b = NULL; - delete af; - delete bf; - } + delete OperationFactor(mul, mulop); } - //Debug("tmp_a : %s", tmp_a->PStr().c_str()); - //Debug("tmp_b : %s", tmp_b->PStr().c_str()); - delete tmp_a; - delete tmp_b; + Optype divop = (op == MULTIPLY) ? DIVIDE : MULTIPLY; + for (auto div : b->m_next[DIVIDE]) + delete OperationFactor(div, divop); + + b->m_next[DIVIDE].clear(); + b->m_next[MULTIPLY].clear(); + return b; } } - // See if operation can be applied to children of this in the same dimension - { - // (a / b) / c = a / (b*c) - // (a * b) / c = a * (b/c) - // (a / b) * c = a / (b/c) - // (a * b) * c = a * (b*c) - // (a + b) + c = a + (b+c) - // (a - b) + c = a - (b-c) - // (a + b) - c = a + (b-c) - // (a - b) - c = a - (b+c) - Optype fwd(op); - Optype rev(invop); - if (op == DIVIDE || op == SUBTRACT) - { - fwd = invop; - rev = op; - } - // opposite direction first (because ideally things will cancel each other out...) - if (m_next[invop] != NULL && m_next[invop]->Operation(b, rev, &append_at) != NULL) - return b; - // forward direction - if (m_next[op] != NULL && m_next[op]->Operation(b, fwd, &append_at) != NULL) - return b; + + bool parent = (merge_point == NULL); + ParanoidNumber * merge = this; + Optype mop = op; + if (parent) + { + merge_point = &merge; + merge_op = &mop; } - - // At this point, we have no choice but to merge 'b' with this ParanoidNumber - - // we are a child; the merge operation needs to be applied by the root, so leave - if (parent != NULL) + else { - if (m_next[op] == NULL) - *parent = this; // last element in list - return NULL; + merge = *merge_point; + mop = *merge_op; } + + Optype invop = InverseOp(op); // inverse of p + Optype fwd = op; + Optype rev = invop; + if (op == DIVIDE) + { + fwd = MULTIPLY; + rev = DIVIDE; + } + + ParanoidNumber * cpy_b = NULL; - append_at->m_next[op] = b; // Merge with b + if (m_next[ADD].size() > 0 || m_next[SUBTRACT].size() > 0) + { + cpy_b = new ParanoidNumber(*b); + } - // MULTIPLY and DIVIDE operations need to be performed on each term in the ADD/SUBTRACT dimension - if (op == DIVIDE || op == MULTIPLY) + for (auto prev : m_next[invop]) { - // apply the operation to each term - if (m_next[ADD] != NULL) delete m_next[ADD]->Operation(new ParanoidNumber(*b), op); - if (m_next[SUBTRACT] != NULL) delete m_next[SUBTRACT]->Operation(new ParanoidNumber(*b), op); - - // try and simplify this by adding the terms (you never know...) - Simplify(ADD); - Simplify(SUBTRACT); + if (prev->OperationFactor(b, rev, merge_point, merge_op) == b) + { + for (auto add : m_next[ADD]) + delete add->OperationFactor(new ParanoidNumber(*cpy_b), op); + for (auto sub : m_next[SUBTRACT]) + delete sub->OperationFactor(new ParanoidNumber(*cpy_b), op); + + delete cpy_b; + return b; + } } - // failed to simplify - return NULL; -} - -bool ParanoidNumber::Simplify(Optype op) -{ - ParanoidNumber * n = m_next[op]; - m_next[op] = NULL; - if (Operation(n, Optype(op))) + for (auto next : m_next[op]) { - delete n; - return true; + if (next->OperationFactor(b, fwd, merge_point, merge_op) == b) + { + for (auto add : m_next[ADD]) + delete add->OperationFactor(new ParanoidNumber(*cpy_b), op); + for (auto sub : m_next[SUBTRACT]) + delete sub->OperationFactor(new ParanoidNumber(*cpy_b), op); + delete cpy_b; + return b; + } } - else + + if (parent) { - m_next[op] = n; - return false; + m_next[op].push_back(b); + for (auto add : m_next[ADD]) + delete add->OperationFactor(new ParanoidNumber(*cpy_b), op); + for (auto sub : m_next[SUBTRACT]) + delete sub->OperationFactor(new ParanoidNumber(*cpy_b), op); } + return NULL; +} + + + +/** + * Performs the operation on a with argument b (a += b, a -= b, a *= b, a /= b) + * @returns b if b can safely be deleted + * @returns NULL if b has been merged with a + * append indicates that b should be merged + */ +ParanoidNumber * ParanoidNumber::Operation(ParanoidNumber * b, Optype op, ParanoidNumber ** merge_point, Optype * merge_op) +{ + + if (b == NULL) + return NULL; + + + if (op == SUBTRACT || op == ADD) + return OperationTerm(b, op, merge_point, merge_op); + if (op == MULTIPLY || op == DIVIDE) + return OperationFactor(b, op, merge_point, merge_op); + return b; } + + string ParanoidNumber::PStr() const { stringstream s; for (int i = 0; i < NOP; ++i) { Optype f = Optype(i); - s << this << OpChar(f) << m_next[f] << "\n"; + s << this; + for (auto n : m_next[f]) + { + s << OpChar(f) << n->PStr(); + } } return s.str(); } +bool ParanoidNumber::Simplify(Optype op) +{ + vector next(0); + swap(m_next[op], next); + for (auto n : next) + { + ParanoidNumber * result = Operation(n, op); + if (result != NULL) + delete result; + else + m_next[op].push_back(n); + } + return (next.size() > m_next[op].size()); +} diff --git a/src/paranoidnumber.h b/src/paranoidnumber.h index 77a8d44..e88cfe7 100644 --- a/src/paranoidnumber.h +++ b/src/paranoidnumber.h @@ -7,6 +7,8 @@ #include #include "log.h" #include +#include +#include #define PARANOID_DIGIT_T float // we could theoretically replace this with a template // but let's not do that... @@ -22,15 +24,8 @@ namespace IPDF (op == DIVIDE) ? MULTIPLY : (op == NOP) ? NOP : NOP); } - inline Optype AdjacentOp(Optype op) - { - return ((op == ADD) ? MULTIPLY : - (op == SUBTRACT) ? DIVIDE : - (op == MULTIPLY) ? ADD : - (op == DIVIDE) ? SUBTRACT : - (op == NOP) ? NOP : NOP); - } + inline char OpChar(int op) { static char opch[] = {'+','-','*','/'}; @@ -86,53 +81,35 @@ namespace IPDF Construct(); for (int i = 0; i < NOP; ++i) { - if (cpy.m_next[i] != NULL) - m_next[i] = new ParanoidNumber(*(cpy.m_next[i])); + for (auto next : cpy.m_next[i]) + m_next[i].push_back(new ParanoidNumber(*next)); } } ParanoidNumber(const char * str); - ParanoidNumber(const std::string & str) : ParanoidNumber(str.c_str()) {Construct();} + ParanoidNumber(const std::string & str) : ParanoidNumber(str.c_str()) {} virtual ~ParanoidNumber(); inline void Construct() { - for (int i = 0; i < NOP; ++i) - m_next[i] = NULL; g_count++; } template T Convert() const; - template T AddTerms(T value = T(0)) const; - template T MultiplyFactors(T value = T(1)) const; - template T Head() const {return (m_op == SUBTRACT) ? T(-m_value) : T(m_value);} - - - double ToDouble() const {return Convert();} - float ToFloat() const {return Convert();} digit_t Digit() const {return Convert();} bool Floating() const { - for (int i = 0; i < NOP; ++i) - { - if (m_next[i] != NULL) - return false; - } - return true; + return NoFactors() && NoTerms(); } bool Sunken() const {return !Floating();} // I could not resist... - bool Pure(Optype op) const - { - if (op == ADD || op == SUBTRACT) - return (m_next[MULTIPLY] == NULL && m_next[DIVIDE] == NULL); - return (m_next[ADD] == NULL && m_next[SUBTRACT] == NULL); - } + bool NoFactors() const {return (m_next[MULTIPLY].size() == 0 && m_next[DIVIDE].size() == 0);} + bool NoTerms() const {return (m_next[ADD].size() == 0 && m_next[SUBTRACT].size() == 0);} ParanoidNumber & operator+=(const ParanoidNumber & a); ParanoidNumber & operator-=(const ParanoidNumber & a); @@ -140,8 +117,10 @@ namespace IPDF ParanoidNumber & operator/=(const ParanoidNumber & a); ParanoidNumber & operator=(const ParanoidNumber & a); - - ParanoidNumber * Operation(ParanoidNumber * b, Optype op, ParanoidNumber ** parent = NULL); + ParanoidNumber * OperationTerm(ParanoidNumber * b, Optype op, ParanoidNumber ** merge_point = NULL, Optype * mop = NULL); + ParanoidNumber * OperationFactor(ParanoidNumber * b, Optype op, ParanoidNumber ** merge_point = NULL, Optype * mop = NULL); + ParanoidNumber * TrivialOp(ParanoidNumber * b, Optype op); + ParanoidNumber * Operation(ParanoidNumber * b, Optype op, ParanoidNumber ** merge_point = NULL, Optype * mop = NULL); bool Simplify(Optype op); @@ -179,6 +158,24 @@ namespace IPDF std::string Str() const; + ParanoidNumber * CopyTerms() + { + ParanoidNumber * copy = new ParanoidNumber(*this); + copy->m_value = 0; + copy->Simplify(ADD); + copy->Simplify(SUBTRACT); + return copy; + } + + ParanoidNumber * CopyFactors() + { + ParanoidNumber * copy = new ParanoidNumber(*this); + copy->m_value = 1; + copy->Simplify(MULTIPLY); + copy->Simplify(DIVIDE); + return copy; + } + static int64_t Paranoia() {return g_count;} @@ -193,69 +190,30 @@ namespace IPDF digit_t m_value; Optype m_op; - ParanoidNumber * m_next[4]; // Next by Operation + std::vector m_next[4]; + + int m_size; }; template -T ParanoidNumber::AddTerms(T value) const -{ - ParanoidNumber * add = m_next[ADD]; - ParanoidNumber * sub = m_next[SUBTRACT]; - while (add != NULL && sub != NULL) - { - value += add->m_value * add->MultiplyFactors(); - value -= sub->m_value * sub->MultiplyFactors(); - add = add->m_next[ADD]; - sub = sub->m_next[SUBTRACT]; - } - while (add != NULL) - { - value += add->m_value * add->MultiplyFactors(); - add = add->m_next[ADD]; - } - while (sub != NULL) - { - value -= sub->m_value * sub->MultiplyFactors(); - sub = sub->m_next[SUBTRACT];; - } - return value; -} - -template -T ParanoidNumber::MultiplyFactors(T value) const +T ParanoidNumber::Convert() const { - ParanoidNumber * mul = m_next[MULTIPLY]; - ParanoidNumber * div = m_next[DIVIDE]; - while (mul != NULL && div != NULL) + T value(m_value); + for (auto mul : m_next[MULTIPLY]) { - value *= (mul->m_value + mul->AddTerms()); - value /= (div->m_value + div->AddTerms()); - mul = mul->m_next[MULTIPLY]; - div = div->m_next[DIVIDE]; + value *= mul->Digit(); } - while (mul != NULL) + for (auto div : m_next[DIVIDE]) { - value *= (mul->m_value + mul->AddTerms()); - mul = mul->m_next[MULTIPLY]; - } - while (div != NULL) - { - value /= (div->m_value + div->AddTerms()); - div = div->m_next[DIVIDE]; + value /= div->Digit(); } + for (auto add : m_next[ADD]) + value += add->Digit(); + for (auto sub : m_next[SUBTRACT]) + value -= sub->Digit(); return value; } - - -template -T ParanoidNumber::Convert() const -{ - return MultiplyFactors(m_value) + AddTerms(0); -} - - - } #endif //_PARANOIDNUMBER_H diff --git a/src/tests/paranoidcalculator.cpp b/src/tests/paranoidcalculator.cpp index b9bd13a..c8f7524 100644 --- a/src/tests/paranoidcalculator.cpp +++ b/src/tests/paranoidcalculator.cpp @@ -29,6 +29,7 @@ int main(int argc, char ** argv) float fa = da; while (cin.good()) { + Debug("a is {%s} \"%.40lf\"", a.Str().c_str(), a.ToDouble()); char op; cin >> op; token = ""; @@ -41,11 +42,12 @@ int main(int argc, char ** argv) token += c; c = cin.get(); } + //Debug("String is \"%s\"", token.c_str()); float fb = strtof(token.c_str(), NULL); double db = strtod(token.c_str(), NULL); ParanoidNumber b(token.c_str()); - Debug("a is {%s} \"%lf\"", a.Str().c_str(), a.ToDouble()); + Debug("b is {%s} \"%lf\"", b.Str().c_str(), b.ToDouble()); Debug("db is %lf", db); switch (op) @@ -74,9 +76,9 @@ int main(int argc, char ** argv) Debug("a is: {%s}", a.Str().c_str()); Debug("a as double: %.40lf", a.ToDouble()); - Debug("a as float: %.40f", a.ToFloat()); - Debug("a as int64_t: %ld", a.Convert()); - Debug("floats give: %.40f", fa); + //Debug("a as float: %.40f", a.ToFloat()); + //Debug("a as int64_t: %ld", a.Convert()); + //Debug("floats give: %.40f", fa); Debug("double gives: %.40lf", da); diff --git a/src/tests/paranoidtester.cpp b/src/tests/paranoidtester.cpp index 84c4bb0..1d4f26a 100644 --- a/src/tests/paranoidtester.cpp +++ b/src/tests/paranoidtester.cpp @@ -13,7 +13,7 @@ using namespace std; using namespace IPDF; -string RandomNumberAsString(int max_digits = 12) +string RandomNumberAsString(int max_digits = 3) { string result(""); int digits = 1+(rand() % max_digits); @@ -30,31 +30,219 @@ string RandomNumberAsString(int max_digits = 12) return result; } -bool CloseEnough(double d, ParanoidNumber & p) +bool CloseEnough(long double d, ParanoidNumber & p, long double eps = 1e-6) { - double pd = p.ToDouble(); + long double pd = p.Convert(); if (d == 0) - return fabs(pd) <= 1e-6; - return fabs((fabs(pd - d) / d)) <= 1e-6; + return fabs(pd) <= eps; + return fabs((fabs(pd - d) / d)) <= eps; +} + +void TestOp(ParanoidNumber & p, double & d, Optype op, const double amount) +{ + string p0str(p.Str()); + double p0 = p.ToDouble(); + switch (op) + { + case ADD: + p += amount; + d += amount; + break; + case SUBTRACT: + p -= amount; + d -= amount; + break; + case MULTIPLY: + p *= amount; + d *= amount; + break; + case DIVIDE: + p /= amount; + d /= amount; + break; + default: + break; + } + if (!CloseEnough(d, p)) + { + Debug("%lf %c= %lf failed", p0, OpChar(op), amount); + Debug("%lf vs %lf", p.ToDouble(), d); + Debug("Before: {%s}\n", p0str.c_str()); + Debug("After: {%s}\n", p.Str().c_str()); + Fatal(":-("); + } + +} + +void TestAddSubIntegers(int max=100) +{ + Debug("Test add/sub integers 0 -> %i", max); + ParanoidNumber p; + double d(0); + for (int a = 0; a < max; ++a) + { + TestOp(p, d, ADD, a); + for (int b = 0; b < max; ++b) + { + TestOp(p, d, SUBTRACT, b); + } + for (int b = 0; b < max; ++b) + { + TestOp(p, d, ADD, b); + } + } + for (int a = 0; a < max; ++a) + { + TestOp(p, d, SUBTRACT, a); + for (int b = 0; b < max; ++b) + { + TestOp(p, d, ADD, b); + } + for (int b = 0; b < max; ++b) + { + TestOp(p, d, SUBTRACT, b); + } + } + Debug("PN Yields: %.40lf", p.ToDouble()); + Debug("Doubles Yield: %.40lf", d); + Debug("Complete!"); + +} + +void TestMulDivIntegers(int max=50) +{ + Debug("Test mul/div integers 1 -> %i", max); + ParanoidNumber p(1.0); + double d(1.0); + for (int a = 1; a < max; ++a) + { + TestOp(p, d, MULTIPLY, a); + for (int b = 1; b < max; ++b) + { + TestOp(p, d, DIVIDE, b); + } + for (int b = 1; b < max; ++b) + { + TestOp(p, d, MULTIPLY, b); + } + } + for (int a = 1; a < max; ++a) + { + TestOp(p, d, DIVIDE, a); + for (int b = 1; b < max; ++b) + { + TestOp(p, d, MULTIPLY, b); + } + for (int b = 1; b < max; ++b) + { + TestOp(p, d, DIVIDE, b); + } + } + Debug("PN Yields: %.40lf", p.ToDouble()); + Debug("Doubles Yield: %.40lf", d); + Debug("Complete!"); + +} + +void TestRandomisedOps(int test_cases = 1000, int ops_per_case = 1, int max_digits = 4) +{ + Debug("Test %i*%i randomised ops (max digits = %i)", test_cases, ops_per_case, max_digits); + long double eps = 1e-6; //* (1e4*ops_per_case); + for (int i = 0; i < test_cases; ++i) + { + string s = RandomNumberAsString(max_digits); + ParanoidNumber a(s); + + double da(a.ToDouble()); + for (int j = 1; j <= ops_per_case; ++j) + { + double da2(a.ToDouble()); + s = RandomNumberAsString(max_digits); + ParanoidNumber b(s); + double db(b.ToDouble()); + + + + Optype op = Optype(rand() % 4); + + ParanoidNumber a_before(a); + + + switch (op) + { + case ADD: + a += b; + da += db; + da2 += db; + break; + case SUBTRACT: + a -= b; + da -= db; + da2 -= db; + break; + case MULTIPLY: + a *= b; + da *= db; + da2 *= db; + break; + case DIVIDE: + if (db == 0) + { + --i; + } + else + { + a /= b; + da /= db; + da2 /= db; + } + break; + case NOP: + break; + } + if (!CloseEnough(da2, a, eps)) + { + Error("{%s} %c= {%s}", a_before.Str().c_str(), OpChar(op), b.Str().c_str()); + Error("{%s}", a.Str().c_str()); + Error("double Yields: %.40lf", da); + Error("PN Yields: %.40lf", a.ToDouble()); + Fatal("Failed on case %i", i*ops_per_case + j-1); + } + } + if (!CloseEnough(da, a, eps)) + { + Warn("double Yields: %.40lf", da); + Warn("PN Yields: %.40lf", a.ToDouble()); + } + } + Debug("Complete!"); + } #define TEST_CASES 1000 int main(int argc, char ** argv) { - srand(time(NULL)); + TestAddSubIntegers(); + TestMulDivIntegers(); + for (int i = 1; i <= 100; ++i) + TestRandomisedOps(1000, i); + return 0; + srand(0);//time(NULL)); //always test off same set string number(RandomNumberAsString()); ParanoidNumber a(number); + float fa = strtof(number.c_str(), NULL); double da = strtod(number.c_str(), NULL); double diff = 0; long double lda = strtold(number.c_str(), NULL); - + Debug("a is %s", a.Str().c_str()); if (fabs(a.ToDouble() - da) > 1e-6) { Error("double %lf, pn %lf {%s}", da, a.ToDouble(), a.Str().c_str()); Fatal("Didn't construct correctly off %s", number.c_str()); + } char opch[] = {'+','-','*','/'}; @@ -109,15 +297,15 @@ int main(int argc, char ** argv) break; } diff = 100.0*(fabs(a.ToDouble() - da) / da); - if (!CloseEnough(da, a)) + if (!CloseEnough(lda, a)) { Error("Op %i: ParanoidNumber probably doesn't work", i); Error("Operation: %lf %c %lf", oldda, opch[op], db); Error("As PN: %lf %c %lf", olda.ToDouble(), opch[op], b.ToDouble()); + Error("PN String before: %s", olda.Str().c_str()); Error("PN String: %s", a.Str().c_str()); - Error("Diff is %.40lf", diff); Error("LONG double gives %.40llf", lda); - Fatal("%.40lf, expected aboout %.40lf", a.ToDouble(), da); + Fatal("%.40llf, expected aboout %.40llf", a.Convert(), lda); } -- 2.20.1