Skip to content

Commit

Permalink
updated for 2v9
Browse files Browse the repository at this point in the history
  • Loading branch information
p-j-miller authored Jun 7, 2022
1 parent f33bccf commit 83f09b1
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 98 deletions.
25 changes: 15 additions & 10 deletions UDataPlotWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@
// 2v8 24/3/2022 : strptime() function added for date/time handling
// : csvsave added % complete and buffering on output to speed up writing to file.
// : csvsave interpolates if required so x values do not need to be identical on all traces
// 2v9 4/6/2022 : bug when X_Offset used and multiple traces added at once then offset is added once for 1st trace, twice for 2nd etc.
// : first_time changed from double to long double so accuracy is improved when "start time from 0" is selected
// : gethms() and gethms_days() also both now return long doubles and are coded to convert numbers to this full resolution.
//
//---------------------------------------------------------------------------
/*----------------------------------------------------------------------------
* Copyright (c) 2019,2020,2021,2022 Peter Miller
Expand Down Expand Up @@ -127,7 +131,7 @@


extern TForm1 *Form1;
const char * Prog_Name="CSVgraph (Github) 2v8e"; // needs to be global as used in about box as well.
const char * Prog_Name="CSVgraph (Github) 2v9c"; // needs to be global as used in about box as well.
#if 1 /* if 1 then use fast_strtof() rather than atof() for floating point conversion. Note in this application this is only slightly faster (1-5%) */
extern "C" float fast_strtof(const char *s,char **endptr); // if endptr != NULL returns 1st character thats not in the number
#define strtod fast_strtof /* set so we use it in place of strtod() */
Expand Down Expand Up @@ -1320,9 +1324,10 @@ void __fastcall TPlotWindow::Button_add_trace1Click(TObject *Sender)
float previousxvalue,previousyvalue;
double median_ahead_t; // >0 for median filtering to be used
int poly_order;
double first_time; // used when reading times in for x axis, store times relative to 1st time
long double first_time; // used when reading times in for x axis, store times relative to 1st time
int nos_traces_added=0;
bool allvalidxvals=true;
double x_offset;
clock_t start_t,end_t;
int nos_errs=0; // count of errors found when reading values
#define MAX_ERRS 2 /* max errors that will be displayyed in full */
Expand Down Expand Up @@ -1462,9 +1467,9 @@ void __fastcall TPlotWindow::Button_add_trace1Click(TObject *Sender)
StatusText->Caption="Invalid xcol";
addtraceactive=false;// finished
return;
}
// now get X offset
double x_offset=atof(AnsiOf(Edit_Xoffset->Text.c_str()));
}
// get x offset from gui to a local variable as a double.
x_offset=atof(AnsiOf(Edit_Xoffset->Text.c_str()));

// ycol can either by an unsigned integer number or an expression OR a comma seperated list of numbers
char *ys;
Expand All @@ -1481,7 +1486,7 @@ void __fastcall TPlotWindow::Button_add_trace1Click(TObject *Sender)
fclose(fin);
StatusText->Caption="Invalid ycol";
addtraceactive=false;// finished
return;
return;
}
ys=s; // save copy so we can free at the end
#if 1
Expand Down Expand Up @@ -1872,7 +1877,7 @@ void __fastcall TPlotWindow::Button_add_trace1Click(TObject *Sender)

// get x value
if(allvalidxvals && nos_traces_added>1 && xmonotonic && !compress ) // This optimisation disabled if lines with invalid xvals found (and skipped)
{xval=pScientificGraph->fnAddDataPoint_nextx(iGraph); // same value as previous graph loaded as this is faster than decoding it again
{xval=pScientificGraph->fnAddDataPoint_nextx(iGraph)-x_offset; // same value as previous graph loaded as this is faster than decoding it again , x_offset will be added later but is already in previous trace so must subtract here
if(!firstxvalue && xval<previousxvalue)
{if(++nos_errs<=MAX_ERRS)
rprintf("Error: adding x value from previous trace and values not monotonic at line %d xval=%g\n",lines_in_file+1,xval);
Expand All @@ -1881,7 +1886,7 @@ void __fastcall TPlotWindow::Button_add_trace1Click(TObject *Sender)
}
}
else
{double ti;
{long double ti; // ti is the time just read in - this needs as much resolution as possible - first_time is also a long double
switch(Xcol_type->ItemIndex)
{case 0: xval=lines_in_file; // x=linenumber in file
break;
Expand Down Expand Up @@ -1983,7 +1988,7 @@ void __fastcall TPlotWindow::Button_add_trace1Click(TObject *Sender)
continue; // ignore line
}
ti=ya_mktime(&my_tm); /* fully functional version of mktime() that returns secs and takes (and changes if necessary) timeptr */
ti+=strp_tz.f_secs;// add in any fractional seconds */
ti+=strp_tz.f_secs;// add in any fractional seconds (ti is a long double to maximise resolution here) */

if(firstxvalue)
{first_time=ti; // remember 1st value, and potentially use as offset for the rest of the values
Expand All @@ -1992,7 +1997,7 @@ void __fastcall TPlotWindow::Button_add_trace1Click(TObject *Sender)
{
/* line below assumes times are in increasing order which is NOT guaranteed ! */
/* However, first_time is a double, as is ti so this potentially offers higher resolution as the difference is stored as a float */
xval=ti-first_time; // ofset time by time of 1st value (so we maximise resolution in the float xval)
xval=ti-first_time; // offset time by time of 1st value [both are long doubles] (so we maximise resolution in the float xval)
}
else
{
Expand Down
2 changes: 1 addition & 1 deletion UDataPlotWindow.dfm
Original file line number Diff line number Diff line change
Expand Up @@ -964,7 +964,7 @@ object PlotWindow: TPlotWindow
Left = 304
Top = 32
Bitmap = {
494C01010A000E00000210001000FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600
494C01010A000E00100210001000FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600
0000000000003600000028000000400000003000000001002000000000000030
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
Expand Down
Binary file modified csvgraph.docx
Binary file not shown.
122 changes: 37 additions & 85 deletions expr-code.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1281,68 +1281,83 @@ bool getfloatge0(char *s, float *d)
return true;
}


/* functions to deal with time input */
#if 1
/* this is a faster version that tries to avoid floating point maths, this reduced load time of 2 columns from 67 secs to 51 secs */
double gethms(char *s)
{/* read a time of the format hh:mm:ss.s , returns it as a double value in seconds */
/* this version uses uint64 and long double to offer reasonable speed, high resolution with no possibility of an overflow */
long double gethms(char *s)
{/* read a time of the format hh:mm:ss.s , returns it as a long double value in seconds */
/* if just a number is found this will be treated as seconds (which can include a decimal point and digits after the dp)
This is not a general purpose numeric input routine, it does not accept a sign, nor an exponent,
but it does accept mumbers of the type nnn.nnn (overall its limited by long double to a total of 18 sf)
if aa:bb is found this will be treated as aa mins and bb secs (which can include a decimal point and digits after the dp)
if aa:bb:cc is found this will be treated as aa hours, bb mins and cc secs (which can include a decimal point and digits after the dp)
returns 0 if does not start with a number, otherwise converts as much as possible based on the above format
This means in particular that trailing whitespace and "'s are ignored
23:59:59 => 86399 secs which is well within the capability of a uint32
23:59:59 => 86399 secs. A long double allows lots of digits after the dp, or a time purely given in seconds.
*/
uint32_t sec=0,sec1=0; // sec1 is current set of digits, sec is previous total
uint64_t sec1=0; // sec1 is current set of digits, sec is previous total uint64 allows 18sf for sec1 which matches long doubles.
long double sec=0;
int power10=0; // exponent
if(!isdigit(*s)) return 0; /* must start with a number */
sec1=(*s++ -'0'); // ascii->decimal for 1st digit
while(isdigit(*s))
{if(sec1==0)
sec1=(*s++ -'0'); // ascii->decimal for 1st digit
else
{if( (sec1&0xf0000000) == 0)
sec1=sec1*10+(uint32_t)(*s++ -'0'); // ascii->decimal in general
{if( (sec1&UINT64_C(0xf000000000000000)) == 0) // 0x1000000000000000=1,152,921,504,606,846,976 so 18sf min
sec1=sec1*10+(uint64_t)(*s++ -'0'); // ascii->decimal in general
else
{s++;
power10++; // too many digits - keep track of decimal point
power10++; // too many digits for uint64 - keep track of decimal point
}
}
if(*s==':' && isdigit(s[1]))
{sec=(sec+sec1)*60; // previous must have been minutes (or hours) so multiply by 60 to get secs [ or mins]
{ // previous must have been minutes (or hours) so multiply by 60 to get secs [ or mins]
if(power10!=0 )
{sec=(sec+(long double)sec1*pow10l(power10))*60.0;
power10=0;
}
else
{sec=(sec+(long double)sec1)*60.0;
}
sec1=0; // ready to get next set of digits
++s; // skip :
}
}
// add in extra seconds from sec1
if(power10!=0)
{return ((double)sec+(double)sec1)*pow10(power10);// already have too many sf so we can ignore dp if its present
/* was pow(10.0,power10), replaced with pow10(power10) */
{sec+= (long double)sec1*pow10l(power10);
}
else
{sec+=(long double)sec1;
}
if(*s=='.')
{ // seconds contains dp , so we now need to keep track of dp and watch out for uint32 overflowing - we have at most 59 secs in sec1 so we have plenty of resolution
{ // seconds contains dp , so we now need to keep track of digits after dp and watch out for uint64 overflowing
++s; // skip dp
while(isdigit(*s) && (sec1&0xf0000000) == 0 )
{sec1=sec1*10+(uint32_t)(*s++ -'0');
sec1=0; // ready to get digits after dp
power10=0;
while(isdigit(*s) && (sec1&UINT64_C(0xf000000000000000)) == 0 )
{sec1=sec1*10+(uint64_t)(*s++ -'0');
power10++; // keep track of decimal point position
}
if(isdigit(*s) && *s>='5') sec1++; // round if more digits present
return (double)sec+(double)sec1/pow10(power10);
return sec+(long double)sec1/pow10l(power10); // if digits after dp
}
return (double)sec+(double)sec1; // if seconds is an integer
return sec; // if seconds is an integer
}

static unsigned int days=0;
static double last_time_secs=0;
static long double last_time_secs=0;
static bool skip=false; // skip 1st number in a big step
void reset_days(void) /* reset static variables for gethms_days() - should be used before using gethms_days() to read times from a file */
{days=0;
last_time_secs=0;
skip=true; // believe 1st value
}

double gethms_days(char *s) /* read time in format hh:mm:ss.s , assumed to be called in sequence and accounts for days when time wraps around. Returns secs, or -ve number on error */
/* this has to return a double as we could have a lot of days and we would quickly run out of resolution with a float */
{double t;
long double gethms_days(char *s) /* read time in format hh:mm:ss.s , assumed to be called in sequence and accounts for days when time wraps around. Returns secs, or -ve number on error */
/* this returns a long double as we could have a lot of days and we would quickly run out of resolution with a float */
{long double t;
if(!isdigit(*s)) return -1; /* should start with a number, return -1 to flag this is an error */
t=gethms(s);
if(t<last_time_secs)
Expand All @@ -1368,75 +1383,12 @@ double gethms_days(char *s) /* read time in format hh:mm:ss.s , assumed to be ca
{ // -ve value of t indicate an error
last_time_secs=t;
if(days!=0)
return t+86400.0*(double)days; /* 86400=24.0*60.0*60.0 , 24 hours a day, 3600 secs in a hour */
return t+86400.0*(long double)days; /* 86400=24.0*60.0*60.0 , 24 hours a day, 3600 secs in a hour */
}
return t;
}
#else
/* original (slower) code */
double gethms(char *s)
{/* read a time of the format hh:mm:ss.s , returns it as a double value in seconds */
/* if just a number is found this will be treated as seconds (which can include a decimal point and digits after the dp)
if aa:bb is found this will be treated as aa mins and bb secs (which can include a decimal point and digits after the dp)
if aa:bb:cc is found this will be treated as aa hours, bb mins and cc secs (which can include a decimal point and digits after the dp)
returns 0 if does not start with a number, otherwise converts as much as possible based on the above format
This means in particular that trailing whitespace and "'s are ignored
*/
double secs,d;
if(!isdigit(*s)) return 0; /* must start with a number */
secs=strtod(s,&s); /* this could be secs or mins(:secs) or hours(:min:secs) */
if(*s==':' && isdigit(s[1]))
{++s; /* skip : , we have 1 or 2:'s*/
d=strtod(s,&s); /* read mins */
secs=secs*60+d; /* convert to secs assuming min:secs */
}
if(*s==':' && isdigit(s[1]))
{++s; /* skip : (2 :'s means we have h:m:s) */
d=strtod(s,&s); /* read secs */
secs=secs*60+d; /* convert to secs */
}
return secs;
}

static unsigned int days=0;
static double last_time_secs=0;
static bool skip=false; // skip 1st number in a big step
void reset_days(void) /* reset static variables for gethms_days() - should be used before using gethms_days() to read times from a file */
{days=0;
last_time_secs=0;
skip=true; // believe 1st value
}

double gethms_days(char *s) /* read time in format hh:mm:ss.s , assumed to be called in sequence and accounts for days when time wraps around. Returns secs, or -ve number on error */
{double t;
if(!isdigit(*s)) return -1; /* should start with a number, return -1 to flag this is an error */
t=gethms(s);
if(t<last_time_secs)
{
if((last_time_secs - t) > 18.0*60.0*60.0 )
{
days++; /* if time appears to have gone > 18 hours backwards assume this is because we have passed into a new day */
skip=false; // assume time is valid
}
else
{if(!skip)
{skip=true;
t= -2;/* time has gone backwards for no reason - return -2 to indicate an error */
}
else skip=false; // repeated , accept value (could be due to a gap in the log [power cut?] that crossed midnight)
}
// rprintf("gethms_day(%s) days=%u last_time=%.1f t=%.1f returns %.1f\n",s,days,last_time_secs,tc,t+24.0*60.0*60.0*(double)days);
}
else skip=false; // value appears to be OK
if(t>0)
{ // -ve value of t indicate an error
last_time_secs=t;
if(days!=0)
t+=24.0*60.0*60.0*(double)days; /* 24 hours a day, 3600 secs in a hour */
}
return t;
}
#endif
/* functions to validate numeric input */
char * validate_num(char *text, float min, float max, float *d,bool *ok, char *onerror)
/* validate input from an edit control etc , returns new value for control [unchanged if OK]*/
Expand Down
4 changes: 2 additions & 2 deletions expr-code.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ bool getfloat(char *s, float *d); /*reads a floating point number returns true i
bool getfloatgt0(char *s, float *d);/* as above but requires number to be >0 */
bool getfloatge0(char *s, float *d);/* as above but requires number to be >0 */

double gethms(char *s); /* read a time of format hh:mm:ss.s , returns time in seconds */
long double gethms(char *s); /* read a time of format hh:mm:ss.s , returns time in seconds */
void reset_days(void); /* reset static variables for gethms_days() - should be used before using gethms_days() to read times from a file */
double gethms_days(char *s); /* read time in format hh:mm:ss.s , assumed to be called in sequence and accounts for days when time wraps around. Returns secs */
long double gethms_days(char *s); /* read time in format hh:mm:ss.s , assumed to be called in sequence and accounts for days when time wraps around. Returns secs */

char * validate_num(char *text, float min, float max, float *d,bool *ok, char *onerror); /* validate input from an edit control etc , returns new value for control [unchanged if OK]*/
TColor cvalidate_num(char *text, float min, float max, float *d,bool *ok); /* validate input from an edit control etc , returns clRED on error*/
Expand Down

0 comments on commit 83f09b1

Please sign in to comment.