Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Precision lost with double numbers #33

Open
mingodad opened this issue Sep 28, 2022 · 11 comments
Open

Precision lost with double numbers #33

mingodad opened this issue Sep 28, 2022 · 11 comments

Comments

@mingodad
Copy link
Contributor

When testing nullc with the tests from https://github.com/ArashPartow/exprtk there is a big difference in the total of equal expressions compared to C/C++ or other programming languages:

import std.io;
import std.math;

int total_count = 0;
int total_eq = 0;

void equal(double a, double b) {
	++total_count;
	if(a == b) {
		++total_eq;
	}
	//else { io.out << a << "<>" << b << io.endl;}
	else { io.out << total_count << "\t";Print(a, 16); io.out << "<>"; Print(b, 16); io.out << io.endl;}
}


double x, y, z, w;

x=11.12345678910737373;
y=22.12345678910737373;
z=33.12345678910737373;
w=44.12345678910737373;
//...
equal((-10.123456789107372000000000000000),(3-(2+abs(x))));
equal((-10.123456789107372000000000000000),(3-(abs(x)+2)));
io.out << "-10.123456789107372" << " == " << -10.123456789107372000000000000000 << " == "; Print(-10.123456789107372000000000000000, 16); io.out << io.endl;

io.out << total_count << "\t" << total_eq << io.endl;

return 0;

Output before :

-10.123456789107372 == -10.123456790947 == -10.1234567909470208
7366	417

Output after:

-10.123456789107372 == -10.123456789107 == -10.1234567891073723
7366	6923

Patch to get a better double precision and also accept numbers like .23, -.34, ...:

--------------------------- NULLC/ExpressionTree.cpp ---------------------------
index 40da9356..f6fd3dbc 100644
@@ -269,7 +269,7 @@ namespace
 	
 		if(*str == '.')
 		{
-			double power = 0.1f;
+			double power = 0.1;
 			str++;
 
 			while((digit = *str - '0') < 10)
@@ -280,7 +280,7 @@ namespace
 			}
 		}
 
-		if(*str == 'e')
+		if(*str == 'e' || *str == 'E')
 		{
 			str++;
 
@@ -3792,7 +3792,7 @@ ExprBase* AnalyzeNumber(ExpressionContext &ctx, SynNumber *syntax)
 
 	for(unsigned i = 0; i < value.length(); i++)
 	{
-		if(value.begin[i] == '.' || value.begin[i] == 'e')
+		if(value.begin[i] == '.' || value.begin[i] == 'e' || value.begin[i] == 'E')
 			isFP = true;
 	}
 

------------------------------- NULLC/Lexer.cpp -------------------------------
index 6ce9c8de..4fcd7649 100644
@@ -132,7 +132,8 @@ void Lexer::Lexify(const char* code)
 			}
 			break;
 		case '.':
-			lType = lex_point;
+                        if(isDigit(code[1])) goto do_lex_number;
+			else lType = lex_point;
 			break;
 		case ',':
 			lType = lex_comma;
@@ -392,6 +393,7 @@ void Lexer::Lexify(const char* code)
 		default:
 			if(isDigit(*code))
 			{
+do_lex_number:                    
 				lType = lex_number;
 
 				const char *pos = code;
@@ -408,7 +410,7 @@ void Lexer::Lexify(const char* code)
 					pos++;
 				while(isDigit(*pos))
 					pos++;
-				if(*pos == 'e')
+				if(*pos == 'e' || *pos == 'E')
 				{
 					pos++;
 					if(*pos == '-')

Full test source
exprtk_functional_test.nc.zip

@mingodad
Copy link
Contributor Author

Probably need to review other places where a double is initialized with a float.

@mingodad
Copy link
Contributor Author

Also for the translated C++ to give the same result the NULLC/ExpressionTranslate.cpp need to be changed:

@@ -510,13 +510,13 @@ void TranslateIntegerLiteral(ExpressionTranslateContext &ctx, ExprIntegerLiteral
 }
 
 void TranslateRationalLiteral(ExpressionTranslateContext &ctx, ExprRationalLiteral *expression)
 {
 	if(expression->type == ctx.ctx.typeFloat)
-		Print(ctx, "((float)%e)", expression->value);
+		Print(ctx, "((float)%.9g)", expression->value);
 	else if(expression->type == ctx.ctx.typeDouble)
-		Print(ctx, "%e", expression->value);
+		Print(ctx, "%.17g", expression->value);
 	else
 		assert(!"unknown type");
 }
 
 void TranslateTypeLiteral(ExpressionTranslateContext &ctx, ExprTypeLiteral *expression)

@mingodad
Copy link
Contributor Author

Hello WheretIB are you still around ?
Is this project dead ?

@WheretIB
Copy link
Owner

I'm still around.

I will check your suggestions this weekend.
Precision fixes sound great.
But I'm not sure about syntax changes like supporting .1 yet, I will have to compare to other languages.

@mingodad
Copy link
Contributor Author

mingodad commented Oct 1, 2022

Thank you for reply !
I'm trying to add goto and struct to nullc and changing a bit the syntax to be more close to C/C++ where possible, like I've added lex_dblcolon :: and in this branch https://github.com/mingodad/nullc/tree/dad I have the declaration of class members outside of a class using it instead of lex_colon and also paln to do the same for namespaces.

There is a reason to not using the same syntax for simple/similar constructs ?

@mingodad
Copy link
Contributor Author

mingodad commented Oct 1, 2022

Would be nice if nullc could accept the subset of C++ that is used to build itself.

@mingodad
Copy link
Contributor Author

mingodad commented Oct 1, 2022

Playing a bit with this project I've add a few nore C++ keywords, most of then are parsed and silently ignored for now, and I can execute this dummy script:

import std.io;

enum chartype
{
	ct_symbol = 64,			// Any symbol > 127, a-z, A-Z, 0-9, _
	ct_start_symbol = 128	// Any symbol > 127, a-z, A-Z, _, :
};

inline int give5() {return 5;}

class Klass {
	int _k;
	static int st = 0;
public:
	void Klass(int x) {
		this._k = x;
	}

	//static
	//void st_add(int x) { Klass::st += x;}
	//static void st_add(int x) { this._k += x;}
	//static
	//int st_get() { return Klass::st;}
protected:
	int get() {return this._k;}
private:
	int set(int x) {int prev = this._k; this._k = x; return prev;}
	//void finalize() {}

};

//static int Klass::st = 0;
//static
//void Klass::st_add(int x) { Klass::st += x;}

struct dad_t {
	int x, y;
};

dad_t dt;
dt.x = 3;
dt.y = 4;

Klass klass = Klass(9);

io.out << dt.x << ":" << dt.y << ":" << int(chartype.ct_symbol) << ":" << klass._k << io.endl;

return 0;

I still need to change the parser to accept access enum elements like in C++ -> chartype::ct_symbol, accept C++ style constructor/destructor(finalize), accept C++ static class members, ...

@mingodad
Copy link
Contributor Author

mingodad commented Oct 1, 2022

With this experiment I want to see how far I can get to allow nullc parse the C++ subset used to build itself.
Probably I would need some help in some places.

WheretIB added a commit that referenced this issue Oct 3, 2022
Sync to internal development branch of October 2022.

Language improvements and changes:
* Fixed precision of double number literals #33
* Default argument values defined in the prototype of a function are available after the body of a function is defined
* Fixed closure of the variable defined inside 'if' condition
* operator definitions in local blocks are no longer visible inside generic functions that are defined in outer scope/imported modules

Library improvements:
* Fixed `vector:sort` for containers of pointers
* Added `strlen` and `strcpy` to `std.string` module for work with null-terminated character arrays
* Fixed issues with `string` class in `std.string` module
* Added string ordering operators <, <=, >, >= to  `std.string` module
* Fixed return value of `strcmp` in `std.string` module on some platforms
* Added `is_derived_from` function #29

Code generation improvements:
* Fixed rare issue when 'phi' merge instruction had different values coming from empty basic blocks
* Global alloca temporaries can be removed by load-store propagation even when they are in global scope

Runtime improvements:
* Fixed GC skipping the last 4 byte object with pointers (possible on 32 bit targets) at the end of a memory block

API changes:
* Removed support for `NULLC_AUTOBINDING` feature that allowed calling external C functions by automatically finding them in the current running binary (using `dlsym`/`GetProcAddress`).
This feature was disabled by default before in CMake builds, but not in Makefile/VS builds.
As a replacement, `nullcSetMissingFunctionLookup` can be used to setup a callback for missing functions and do the same thing manually.

Other changes:
* Build time statistics tracking
* Big improvements to build time of large multi-module nullc projects
* Added support for additional import paths in nullcl -c/-x options
* Made it easier to use translation to C++ in nullcl
* Fixed missing math library link when nullcl -x is used #28
* Added C++ bindings for `std.memory` module translation to C++
@WheretIB
Copy link
Owner

WheretIB commented Oct 3, 2022

I have applied your fix to the precision of double number parsing, thank you for that.

I'm going to leave this issue open to address the changes to number parsing and to look into the remaining precision issues (maybe I'll just switch to strtod for a full match).

Your fork looks interesting, those are some big changes. You might call it nullc++ :)

@mingodad
Copy link
Contributor Author

mingodad commented Oct 3, 2022

Also I found something strange when testing with exprtk tests, when having the the tests in the global scope it takes almost twice to process than when they are inside a function see attached
exprtk_functional_test2.nc.zip
.

Do you want to join effort on it (nullc++) ?
I've got template on class/struct somehow working now:

import std.io;

enum chartype
{
	ct_symbol = 64,			// Any symbol > 127, a-z, A-Z, 0-9, _
	ct_start_symbol = 128	// Any symbol > 127, a-z, A-Z, _, :
};

inline int give5() {return 5;}

/*
template<typename T>
T createSome() {
	T x = new T();
	return x;
}
*/

template<typename T, classname Z>
class TName {
	T name;
	Z param;
};

typedef TName<char[], int> TNameCI;

TNameCI createSome() {
	return TNameCI();
}

TNameCI nci = createSome();
nci.name = "dad";
nci.param = 5;

struct A extendable {
	int aa;
	void A() {this.aa = 0;}
	int inc() {return ++this.aa;}
}

class Klass : public A {
	int _k;
	static int st = 0;
public:
	void Klass(int x) {
		this._k = x;
	}

	int inc() override {this.aa += 2; return this.aa;}
	//static
	//void st_add(int x) { Klass::st += x;}
	//static void st_add(int x) { this._k += x;}
	//static
	//int st_get() { return Klass::st;}
protected:
	int get() {return this._k;}
private:
	int set(int x) {int prev = this._k; this._k = x; return prev;}
	//void finalize() {}

};

//static int Klass::st = 0;
//static
//void Klass::st_add(int x) { Klass::st += x;}

struct dad_t {
	int x, y;
};

dad_t dt;
dt.x = 3;
dt.y = 4;

Klass klass = Klass(9);

io.out << dt.x << ":" << dt.y << ":" << int(chartype.ct_symbol) << ":" << klass._k << io.endl;

return 0;

@mingodad
Copy link
Contributor Author

mingodad commented Oct 3, 2022

Also when trying to accepts constructor/destructor C++ syntax I noticed the restriction on finalizer (destructor) for stack instances, is this a hard restriction or can it be relaxed ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants