Link to home
Start Free TrialLog in
Avatar of Paul Sinnema
Paul Sinnema

asked on

How to calculate values for charrulers with float?

I'm writing a widget for Uniface. I'm already pretty far but stumble accross a problem calculating minimum and maximum values as well as the steps in between. The chart has to manage float values. For instance:

the chart displays the following values:

x = 1; y = 12,5
x = 2; y = 21,7
y = 3; y = 30,5

The chart should get a y-axes with on top the value 40 and on the way down you should see the values 30, 20, 10 and of course 0 (zero). It should also work with negative values. My problem is: How do I determine what are the minimum and maximum values using floats. It's easy with integers but with floats its different.

Does any of you people have a routine lying arround that does my trick?

Thanks Paul.
Avatar of Brain2000
Brain2000
Flag of United States of America image

This should be fairly simple and work the same as integers.  Something similar to this should do the trick:


#include <stdio.h>

void main(void);
float get_highest(float [],int);
float get_lowest(float [],int);

void main(void)
{
      float x[10]={0,1,2,3,4,5,6,7,8,9};

      printf("highest=%f\n",get_highest(x,10));
      printf("lowest =%f\n",get_lowest(x,10));
}

float get_highest(float a[], int num_of_floats)
{
  int x;
  float highest=0;

  for(x=0;x<num_of_floats;x++)
    if(a[x]>highest) highest=a[x];

  return(highest);
}

float get_lowest(float a[], int num_of_floats)
{
  int x;
  float lowest=9999;

  for(x=0;x<num_of_floats;x++)
    if(a[x]<lowest) lowest=a[x];

  return(lowest);
}
Avatar of ozo
What makes it different with floats if it's easy with integers?
Avatar of Paul Sinnema
Paul Sinnema

ASKER

Thanks for the reply

You're anwswer is not the solution to my problem. I know how to find the lowest and highest in an array but that is not the issue. What I need is to get the nearest higher whole value and the nearest lowest whole value as mentioned in the question above

quote:

x = 1; y = 12,5
x = 2; y = 21,7
y = 3; y = 30,5

The chart should get a y-axes with on top the value 40 and on the way down you should see the values 30, 20, 10 and of course 0 (zero). It should also work with negative values. My problem is: How do I determine what are the minimum and maximum values using floats. It's easy with integers but with floats its different.

end quote

The values should be whole (like 40) whithout the exponent (not 12,5!).

Paul.
lowest = 10*floor(lowest/10);
highest = 10*ceil(highest/10);
Oh, I'm sorry.  I modified the routines to return int values.  Note the get_highest() function returns the next number higher only if needed.

#include <stdio.h>

void main(void);
int get_highest(float [],int);
int get_lowest(float [],int);

void main(void)
{
      float x[10]={0.0,1,2,3,4,5,6,7,8,9};
      printf("highest=%d\n",get_highest(x,10));
      printf("lowest =%d\n",get_lowest(x,10));
}

int get_highest(float a[], int num_of_floats)
{
  int x;
  char temp[100];
  float highest=0;

  for(x=0;x<num_of_floats;x++)
    if(a[x]>highest) highest=a[x];

  sprintf(temp,"%0.0f",highest);
  x=atoi(temp);
  if(highest>(float)x) x++;

  return(x);
}

int get_lowest(float a[], int num_of_floats)
{
  int x;
  char temp[100];
  float lowest=9999;

  for(x=0;x<num_of_floats;x++)
    if(a[x]<lowest) lowest=a[x];

  sprintf(temp,"%0.0f",lowest);
  x=atoi(temp);

  return(lowest);
}
No, that doesn't do the trick either. What if my array was filled with the following values:

x = 1; y = 0,00028
x = 2; y = 0,00034
x = 3; y = 0,00065
etc..

Paul.
In that case I would want something like a ruler with the values

0,00010
0,00020
0,00030
0,00040
0,00050
0,00060
0,00070

Paul.
#include <math.h>
tens = pow(10,floor(log10(highest-lowest)));
lowest = tens*floor(lowest/tens);
highest = tens*ceil(highest/tens);
OK!  I think I've got it.  This time you call the function rule_of_thumb with your lowest and highest values, then how many lines you want in the grid, and then 3 pointers to floats.  It will fill in the start, end, and then step values.  In this example, it does the following:

y=.01
y=.02
y=.03
y=.04
y=.05
y=.06
y=.07
y=.08
y=.09
y=.10

with 20 lines on your graph.

Your graph should go from .01 to .10 with a step of .0045

I've tried other scenarios and it seems to work.  Let me know what you think.


#include <stdio.h>

void main(void);
float get_highest(float [],int);
float get_lowest(float [],int);
void rule_of_thumb(float,float,int,float *,float *,float *);

void main(void)
{
  float h,l;
  float start,end,step;

  float nums[10]={.01,.02,.03,.04,.05,.06,.07,.08,.09,.10};
  printf("highest=%f\n",h=get_highest(nums,10));
  printf("lowest =%f\n",l=get_lowest(nums,10));

  rule_of_thumb(l,h,20,&start,&end,&step);
  printf("START=%f END=%f STEP=%f\n",start,end,step);
}

float get_highest(float a[], int num_of_floats)
{
  int x;
  float highest=0;

  for(x=0;x<num_of_floats;x++)
    if(a[x]>highest) highest=a[x];

  return(highest);
}

float get_lowest(float a[], int num_of_floats)
{
  int x;
  float lowest=9999;

  for(x=0;x<num_of_floats;x++)
    if(a[x]<lowest) lowest=a[x];

  return(lowest);
}

void rule_of_thumb(float lowest,float highest,int num_grid_lines,float *start,float *end,float *step)
{
  int multiplier=0,x,y,z;
  char temp[100];

  while(1)
  {
    sprintf(temp,"%0.0f",highest);
    x=atoi(temp);
    sprintf(temp,"%0.0f",lowest);
    y=atoi(temp);
    if((float)x!=(float)highest || (float)y!=(float)lowest)
    {
      highest*=10.0;
      lowest*=10.0;
      multiplier++;
    }
    else break;
  }

  x=(x/10*10==x?x:x/10*10+10);
  y=y/10*10;
  *start=(float)y;
  *end=(float)x;
  *step=(*end-*start)/(float)num_grid_lines;

  while(multiplier--)
  {
    *start/=(float)10.0;
    *end/=(float)10.0;
    *step/=(float)10.0;
  }
}
what steps do you expect for following values:
a) y1=42.000042;
    y2=42:000043;
    y3=420000.43;
b) y1=42.000042;
    y2=42000.000042;
    y3=.000042
c) y1=-42.000042;
    y2=.042;
    y3=420000.00042;
(choose the size of the fraction as you like)
... and many more such ranges
Ok, after trying relatively larger numbers (like 42000.00042) I found my function above breaks.  So I rewrote it to work with a more dynamic range of numbers.  Unfortunately, I had to move it to a text string in order to do so.  But this should work better:

void rule_of_thumb(float lowest,float highest,int num_grid_lines,float *start,float *end,float *step)
{
  int multiplier=0,z,zz;
  float x,y;
  char high_str[100],low_str[100],*ptr;

  sprintf(high_str,"%f",highest);
  sprintf(low_str,"%f",lowest);

  if(ptr=strchr(high_str,'.'))  //does high have a decimal?
  {
    z=strlen(ptr+1); //how many digits is after the decimal?
    multiplier+=z; //keep track of how many places we've moved
    memmove(ptr,ptr+1,strlen(ptr+1)+1);  //remove decimal
    if(ptr=strchr(low_str,'.')) //move the low's decimal over the same amount
    {
      while(strlen(ptr+1)<z) strcat(ptr,"0");
      memmove(ptr,ptr+1,strlen(ptr+1)+1);
    }
    else
    {
      while(z--) strcat(low_str,"0");
    }
  }

  if(ptr=strchr(low_str,'.'))  //does low have a decimal?
  {
    z=strlen(ptr+1); //how many digits is after the decimal?
    multiplier+=z; //keep track of how many places we've moved
    memmove(ptr,ptr+1,strlen(ptr+1)+1);  //remove decimal
    if(ptr=strchr(high_str,'.')) //move the high's decimal over the same amount
    {
      while(strlen(ptr+1)<z) strcat(ptr,"0");
      memmove(ptr,ptr+1,strlen(ptr+1)+1);
    }
    else
    {
      while(z--) strcat(high_str,"0");
    }
  }
  //set the high/low values now
  low_str[strlen(low_str)-1]='0';
  if(high_str[strlen(high_str)-1]!='0') high_str[strlen(high_str)-2]++;
  high_str[strlen(high_str)-1]='0';

  //put the decimals back in now.
  memmove(low_str+strlen(low_str)-multiplier+1,low_str+strlen(low_str)-multiplier,multiplier+2);
  low_str[strlen(low_str)-multiplier-1]='.';
  memmove(high_str+strlen(high_str)-multiplier+1,high_str+strlen(high_str)-multiplier,multiplier+2);
  high_str[strlen(high_str)-multiplier-1]='.';

  *start=atof(low_str);
  *end=atof(high_str);
  *step=(*end-*start)/(float)num_grid_lines;
}
Wauw, youre really making work of this. I'll test the code and be back to you
I've written the following code to call your routine. It gives unpredictable anwers. I've compiled with Borland C++ 4.5

main ()

{
      float      start,
                  end,
                  step;

      rule_of_thumb ( 42.0, 50.0, MAX_RANGE, &start, &end, &step );

      printf ( " 42.0     50.0    Start %f end %f step %f\n", start, end, step );

      rule_of_thumb ( 42.0019, 120.0125,       MAX_RANGE, &start, &end, &step );

      printf ( " 42.0019 120.0125 Start %f end %f step %f\n", start, end, step );
}

Results in:

 42.0     50.0    Start -6140.000000 end -14332.000000 step -2048.000000
 42.0019 120.0125 Start -1.000000 end -513.000000 step -128.000000
PS: MAX_RANGE is defined as 4
In reply to your question:

a) y1=42.000042;
    y2=42:000043;
    y3=420000.43;
b) y1=42.000042;
    y2=42000.000042;
    y3=.000042
c) y1=-42.000042;
    y2=.042;
    y3=420000.00042;
(choose the size of the fraction as you like)
.... and many more such ranges

start end step
a  40  50   10
b  00  50   10
c  00  50   10

Step would of course depend on the number of steps taken. But when there's room enough it could be smaller (like 5) or bigger (25 in case b and c) if there's less room.
I compiled it with VC.  Let me try the same numbers you did.  If I get the right results, let me see if I can crank my warnings up and make sure I haven't overlooked any compiler specific things.
It becomes more difficult if only small values are available:

a y1 = 0.00125
  y2 = 0.00075
  y3 = 0.00325

a start 0.00050
  end   0.00350
  step  0.00050 or 0.00100
Take a look at how Excel handles this
Oops made a mistake in my answer

In reply to your question:

a) y1=42.000042;
    y2=42:000043;
    y3=420000.43;
b) y1=42.000042;
    y2=42000.000042;
    y3=.000042
c) y1=-42.000042;
    y2=.042;
    y3=420000.00042;
(choose the size of the fraction as you like)
..... and many more such ranges

start    end     step
a  0  500000    50000 or 100000
b  0   50000     5000 or  10000
c  0  500000    50000 or  50000

Steps would of course depend on the number of steps taken. But when there's room enough it could be smaller or bigger if there's less room.
>  It should also work with negative values.
in your question, but
> c  0  500000    50000 or  50000
in last example.
Could you explain? (c) y1=-42.000042; is negativ)

And how to calculate the step?
  fixed number of steps
  increment based on range
or what?
What do you expect to see example a) and c)  (IMHO only 2 points)?

Are there more than 3 values?
>It becomes more difficult if only small values are available:

>a y1 = 0.00125
>  y2 = 0.00075
>  y3 = 0.00325

>a start 0.00050
>  end   0.00350
>  step  0.00050 or 0.00100


This changes things a bit.  I wrote something that would return a the following for the above:

start .00070
end   .00130

That may be something that has to be tweaked a bit to affect the granularity of the start/end.  I currently work with 10, but perhaps a definable value would be more appropriate.  In your case, you used 50.
Ok, I found a couple small problems.  One is I wasn't finding the start/end numbers correctly in all cases.  Also, I found that floats having decimals added to them when passed as variables.  I don't know why, but when I change everything to double, it works fine.  Here is my results from your 2 tests above:

 42.0     50.0    Start 40.000000 end 50.000000 step 2.500000
 42.0019 120.0125 Start 42.001000 end 120.013000 step 19.503000

Also, here is the new rule_of_thumb() function with doubles in place and a couple other fixes.

void rule_of_thumb(double lowest,double highest,int num_grid_lines,double *start,double *end,double *step)
{
  int multiplier=0,z,zz;
  double x,y;
  char high_str[100],low_str[100],*ptr;

  sprintf(high_str,"%f",highest);
  sprintf(low_str,"%f",lowest);

  if(ptr=strchr(high_str,'.'))  //does high have a decimal?
  {
    z=strlen(ptr+1); //how many digits is after the decimal?
    multiplier+=z; //keep track of how many places we've moved
    memmove(ptr,ptr+1,strlen(ptr+1)+1);  //remove decimal
    if(ptr=strchr(low_str,'.')) //move the low's decimal over the same amount
    {
      while(strlen(ptr+1)<z) strcat(ptr,"0");
      memmove(ptr,ptr+1,strlen(ptr+1)+1);
    }
    else
    {
      while(z--) strcat(low_str,"0");
    }
  }

  if(ptr=strchr(low_str,'.'))  //does low have a decimal?
  {
    z=strlen(ptr+1); //how many digits is after the decimal?
    multiplier+=z; //keep track of how many places we've moved
    memmove(ptr,ptr+1,strlen(ptr+1)+1);  //remove decimal
    if(ptr=strchr(high_str,'.')) //move the high's decimal over the same amount
    {
      while(strlen(ptr+1)<z) strcat(ptr,"0");
      memmove(ptr,ptr+1,strlen(ptr+1)+1);
    }
    else
    {
      while(z--) strcat(high_str,"0");
    }
  }

  //set the high/low values now
  z=strlen(low_str)-1;
  zz=strlen(high_str)-1;
  while(low_str[z]=='0' && high_str[zz]=='0')
  {
    if(!--z) break;
    if(!--zz) break;
  }
  low_str[z]='0';
  if(high_str[zz]!='0') high_str[zz-1]++;
  high_str[zz]='0';

  //put the decimals back in now.
  memmove(low_str+strlen(low_str)-multiplier+1,low_str+strlen(low_str)-multiplier,multiplier+2);
  low_str[strlen(low_str)-multiplier-1]='.';
  memmove(high_str+strlen(high_str)-multiplier+1,high_str+strlen(high_str)-multiplier,multiplier+2);
  high_str[strlen(high_str)-multiplier-1]='.';

  *start=atof(low_str);
  *end=atof(high_str);
  *step=(*end-*start)/(double)num_grid_lines;
}
I'm sorry despite all your programming effort I'm still no closer to home. Maybe it's not clear what the problem is

What I want is drawn below

  500 |
      |
  400 |
      |
  300 |
      |
  200 |
      |
  100 |
      |
     0 ------------------------------
      |
 -100 |

etc...

The (in this case Y ) ruler should have whole values not broken ones. The highest value = 500, the lowest -100 the step = 100 there are 7 steps (including the high en low values)

The range that's drawn in the chart could be someting like

x = 1 ; y = 495
x = 2 ; y = 325
x = 3 ; y = 205
x = 4 ; y = -90

or


  0.010 |
        |
  0.008 |
        |
  0.006 |
        |
  0.004 |
        |
  0.002 |
        |
  0.000  ------------------------------
        |
 -0.002 |

Paul.
ten = pow(10,floor(log10(highest-lowest)));
five = 5*pow(10,floor(log10((highest-lowest)/5)));
two = 2*pow(10,floor(log10((highest-lowest)/2)));
step=(two<five)?(two<ten?two:ten):(five<ten?five:ten);
highest=step*ceil(highest/step);
lowest=step*floor(lowest/step);
as you see in ozo's answer, your complicated problem may have a simple solution ;-)
Paul Sinnema, please reread my comments and tell us which and how many values you have to put in the chart, and what's the distribution of the values (see my examples).
Ozo's answer realy is beyond my mathematical knowledge. Can you implemented in your routine.

Since the developer, that is going to use the widget, can resize the chart and enter the values, the number of steps depends on the size of the horizontal and vertical axes. Lets say I will calculate the maximum number of steps, the routine then tells me how many it realy are and what the value is of each step.

By the way. Who am I going to give the points afterward. You're realy working as a team.

Thanks for the energy guys!
ASKER CERTIFIED SOLUTION
Avatar of ozo
ozo
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Great ozo!

I can't determine why it works, but it does. How did you come to this solution. If I had made a solution it would have taken a lot more code. Can you explain why it works as it does?

Paul.
Thanks ahoffmann for your effort.
Thanks brain2000 for the effort