Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

MrFusion

macrumors 6502a
Original poster
Jun 8, 2005
613
0
West-Europe
Actually it's the limitations in representing numbers.
I am working on a custom NSView and drawRect function. Cocoa, in particular the NSBezierPath only cares about floats up to a few digits after the decimal point.

At some point in the drawing, I calculate a set of positions for my path - based on the previous positions - and compare them to some predefined maximum value.
e.g.
0.1 < 1.5
0.1+0.2 < 1.5

Fine, floats can handle that I thought.

---
<edit>:
Safari, damn it. Apple-] is for indenting, not for posting half baked questions.
(see other post for the actual question)
 

lee1210

macrumors 68040
Jan 10, 2005
3,182
3
Dallas, TX
You might want to post a code snippet where that sort of code is failing.

I at least believe something is failing, you didn't ask a question. You shouldn't need to parenthesize those kind of statements as + has a higher precedence than <. Since i don't know what the particular case is you are working on, i made sure that essentially that exact code worked as expected, but with variables rather than literals. I'm not sure what compiler you're working with and even if I did I may not be able to quickly find documentation on whether or not real number literals are parsed as doubles or floats. If doubles, and you are comparing a literal to some float variables that could be causing some strange issue, but it really shouldn't. Things should be implicitly casted as needed, but you might want to throw explicit casts to float in to be positive.

Again, feel free to toss some specific code up and I'm sure someone can point you in the right direction.

-Lee
 

MrFusion

macrumors 6502a
Original poster
Jun 8, 2005
613
0
West-Europe
aargh... rounding errors are killing me

I am working on a custom NSView and drawRect function. Cocoa, in particular the NSBezierPath only cares about floats up to a few digits after the decimal point.

At some point in the drawing, I calculate a set of positions for my path - based on the previous positions - and compare them to some predefined maximum value.
The problem is that e.g. 0.1 is not represented as 0.1 but as 0.100000001. Since small errors keep adding up, my end value is slightly over the top. e.g. 0.800000234 <= 0.8 should be yes for me.

Such accuracy is ridiculously for NSBezierPath's and I also don't need it.
I tried encapsulating it in NSNumber and using compare: to no avail.
I tried roundf(100*value) <= roundf(100*maxValue) to no avail. Even if the representation is not accurately, it should be the same representation.

Now I appreciate the comment from my numerical analysis teacher whom told the class to stay away from programming our own numerical software. No, I didn't do CS and usually I am already happy if my experiments and data give me the order of magnitude of some measured or calculated variable.

Any help or advice for this idiot is welcome. :)
 

MrFusion

macrumors 6502a
Original poster
Jun 8, 2005
613
0
West-Europe
Is there a way you can use a double value instead of a float value?

I probably could, but double also has the same problem, it's just less of an error percentage wise.
Instead of 0.1000001 you have 0.1000000000000001 with double.
Besides NSBezierpath's expect float values.
Comparing two floats for equality can be done by subtracting them and checking if they are smaller than some small number e.g. fabsf(a-b)< c
That much I know.
 

MDMstudios

macrumors member
Mar 18, 2008
36
0
I probably could, but double also has the same problem, it's just less of an error percentage wise.
Instead of 0.1000001 you have 0.1000000000000001 with double.
Besides NSBezierpath's expect float values.
Comparing two floats for equality can be done by subtracting them and checking if they are smaller than some small number e.g. fabsf(a-b)< c
That much I know.
Hmm, is there some type of method you can use for rounding?
 

MDMstudios

macrumors member
Mar 18, 2008
36
0
Is it possible you could make a method like this?

int x;
float y;

x = y * 10;
y = x * .1;

I have used a method like this a few times, and over all it seems like it works pretty good.
 

MrFusion

macrumors 6502a
Original poster
Jun 8, 2005
613
0
West-Europe
Is it possible you could make a method like this?

int x;
float y;

x = y;
y = x * .1;

I have used a method like this a few times, and over all it seems like it works pretty good.

int x1 = 100*y;
int x2 = 100*max;
if (x1 <= x2) {;}

I could do this in my code, I think, but is it safe to just cast a float as an int? I'll play around with this.

Thanks for the comments, MDMstudios, it helps.
 

MDMstudios

macrumors member
Mar 18, 2008
36
0
int x1 = 100*y;
int x2 = 100*max;
if (x1 <= x2) {;}

I could do this in my code, I think, but is it safe to just cast a float as an int? I'll play around with this.

Thanks for the comments, MDMstudios, it helps.

I think it should be safe, and if it puts up a error or warning you could allways use the method
[y intValue];
[x floatValue];

Glad you found my comments helpful.
 

lee1210

macrumors 68040
Jan 10, 2005
3,182
3
Dallas, TX
this may work better than rounding, per se.
multiply the float by 10^# digits that matter to the right of the decimal. Add .5. This helps in the case that the in-memory representation is a little less rather than a little more than the exact value. Call floor on the result, and cast the result of floor to an int, and assign to a temp int. Repeat for all of the values, then compare to 10^# sig dig to the right of the decimal times the limit. Then do an integer <. Use a long or unsigned int if needed for the temp values.

-lee

Ps I'd write some code, but am on my phone. Hopefully the above is good enough to get you started.

.. Too slow. The above should work.
 

gnasher729

Suspended
Nov 25, 2005
17,980
5,565
If you write a loop like

for (x = 0.0; x <= 1.5; x += 0.1) ...

you can never be sure how often it will be executed, because eventually x will have a value that is very close to 1.5, but it could be a tiny bit less, or a tiny bit more. Using double or long double instead of float won't help, x will be a lot closer to 1.5, but it will still be a tiny bit less or a tiny bit more. The solution is very simple: Use an integer variable.

int i;
double x;

for (i = 0; i <= 15; ++i) { x = i * 0.1; ... }
 

MrFusion

macrumors 6502a
Original poster
Jun 8, 2005
613
0
West-Europe
You might want to post a code snippet where that sort of code is failing.
Again, feel free to toss some specific code up and I'm sure someone can point you in the right direction.

-Lee

Sure, here is some of my 1000+ lines of spaghetti code (of version 6 or so). I am not sure I am happy with this version, too much cruft because of these floating point errors.

The idea is to have a NSView that can:
plot any number of arbitrary 2D xy-data,
plot any number of arbitrary 2D functions,
plot linear or logaritmic scales
scale the data (e.i. different units: Hz, kHz, mHz)
plot not only lines between two points, but also have a symbol (circle, square, x,+,...) at that point
Plot the legend with the correct label, symbol, line thickness, color
track the mouse position, and display it on screen
with as many user adjustable options as reasonable possible
zoom in

These are things that in some way or another are working.

Is not possible:
arbitrary positioning of title, labels, legend
editing title, labels, legend directly on the PlotView

The next steps is to include:
converting mouse clicks to marked points

A subclass could be
2D colorplots or barplots

I am not going to do 3D plots. Such plots are also usually pretty horrendous.

There will also be an inspector, while most responsibility of the PlotView is offloaded to a delegate and datasource in such a way that the user doesn't have to see the PlotView code. Like an NSTableView works.

Yes, I know a few other frameworks are out there, but these frameworks don't seem to be orientated towards scientific plots, and besides it's fun to do and I am learning a lot about software design.
When I am either happy or bored with it, maybe I put it in a repository.

Yes, I know there is also exell, numbers, origin, gnuplot, octave, (R, mathematica,) which I use for my real data.

Code:
-(void) drawYAxis {
	if ([delegate showYAxis]){
		//get/set (path) attributes
		NSBezierPath *path = [NSBezierPath bezierPath];
		[path setLineWidth:[delegate borderThickness]];
		[[delegate borderColor] set];
		float ticLength = [delegate axisLength];
		
		//minimum font & rect size (ensure enough spacing between labels)
		NSSize minimumsize = [self minimumLabelsize];
		NSRect rect = NSMakeRect(0,
								 plotarea.origin.y-3*minimumsize.height,
								 0,
								 minimumsize.height);			
		if (yRange.type == 1){
			//linear
			//starting point
			float minCoordStart = fmaxf(nearbyintf(yRange.min),ceilf(yRange.min));
			//draw
			float y;
			float yCoord = minCoordStart;
			while (roundf(100*yCoord) <= roundf(100*yRange.max)){
				//convert
				y = [self transformY:yCoord
								  to:ATRPlotcoordinates];
				//path
				[path moveToPoint:NSMakePoint(plotarea.origin.x,y)];
				[path lineToPoint:NSMakePoint(plotarea.origin.x+ticLength/2,y)];
				//text
				NSAttributedString *str = [self allocLabel:yCoord
												   forAxis:yaxis];//DO NOT FORGET TO RELEASE str
				if (roundf(100*y) > roundf(100*(rect.origin.y+rect.size.height+minimumsize.height))) {
					//enough clearning in relation to previous string
					//update rect
					NSRect newRect;
					newRect.origin.x = plotarea.origin.x-padding-[str size].width;
					newRect.origin.y = y-[str size].height/2;
					newRect.size.width = [str size].width;
					newRect.size.height = [str size].height+minimumsize.height;
					if ([self rectFallsWithinCanvasarea:newRect]) { 
						//rect falls within canvasarea, will be displayed completely 
						newRect.size.height -= minimumsize.height;
						[str drawInRect:newRect];
						rect = newRect;
						[path lineToPoint:NSMakePoint(plotarea.origin.x+ticLength,y)]; //full tic length for labeled tics
					}
				}
				[str release];
				yCoord+=yRange.interval;
			}
			
		} else if (yRange.type == 2){
			//log
			//starting point
			float minCoordStart = yRange.min;
			
			//magnitude -> interval
			float y;
			int magnitude = [self orderOfMagnitude:minCoordStart];
			float interval=powf(10,magnitude);
			//startvalue
			float yCoord = interval;
			int i = 1;
			while (yCoord < minCoordStart){
				yCoord +=interval;
				i++;
			}
			
			while (roundf(100*yCoord) <= roundf(100*yRange.max)){
				//convert
				y = [self transformY:yCoord
								  to:ATRPlotcoordinates];
				//path
				[path moveToPoint:NSMakePoint(plotarea.origin.x,y)];
				if (i == 1)
					[path lineToPoint:NSMakePoint(plotarea.origin.x+ticLength,y)];
				else
					[path lineToPoint:NSMakePoint(plotarea.origin.x+ticLength/2,y)];
				//text
				if (i == 1 | i == 5){
					//text
					NSAttributedString *str = [self allocLabel:yCoord
													   forAxis:yaxis];
					if (roundf(100*y) > roundf(100*(rect.origin.y+rect.size.height+minimumsize.height))) {
						//enough clearning in relation to previous string
						//update rect
						NSRect newRect;
						newRect.origin.x = plotarea.origin.x-padding-[str size].width;
						newRect.origin.y = y - [str size].height/2;
						newRect.size.width = [str size].width;
						newRect.size.height = [str size].height + minimumsize.height;
						if ([self rectFallsWithinCanvasarea:newRect]) { 
							//rect falls within canvasarea
							newRect.size.height -= minimumsize.height;
							[str drawInRect:newRect];
							rect = newRect;
						}
					}
					[str release];
				}
				//adjust interval as appropiate 
				i++;
				yCoord += interval;
				if (i > 9) {
					i=1;
					interval = 10*interval;
				}
			}
		}
		[path stroke];
	}
}

-(BOOL) rectFallsWithinCanvasarea:(NSRect) rect {
	//is rect located in the total available area?
	//top and right side fall within available area (origin + size)
	if (roundf(100*(rect.origin.x+rect.size.width)) < roundf(100*canvasarea.origin.x))
		return NO;	//todo: is view origin not always (0,0)? -> just check for sign?
	if (roundf(100*(rect.origin.y+rect.size.height)) < roundf(100*canvasarea.origin.y))
		return NO;	
	if (roundf(100*(rect.origin.x+rect.size.width)) > roundf(100*(canvasarea.origin.x+canvasarea.size.width)))
		return NO;	
	if (roundf(100*(rect.origin.y+rect.size.height)) > roundf(100*(canvasarea.origin.y+canvasarea.size.height)))
		return NO;	
	//does origin fall within canvasarea
	return [self pointFallsWithinCanvasarea:rect.origin];	
}
-(int) orderOfMagnitude:(float)value {
	int magnitude = 0;
	if (value < 1) {
		while (value < 1){
			magnitude--;
			value = value*10;
		}
	} else {
		float integralPart; 
		modff(value,&integralPart);
		div_t breuk = div(integralPart,10);
		while (breuk.quot > 0){
			magnitude++;
			breuk = div(breuk.quot,10);
		}
	}
	return magnitude;
}

-(void) prepareScalefactors {
	//this should not take scaling into account. Scaling is only for datapoints, this fct is for all points
	//x scale
	if (xRange.type == ATRLinear)
		ratio.x = plotarea.size.width/(xRange.max-xRange.min); //linear
	else if (xRange.type == ATRLogaritmic)
		ratio.x = plotarea.size.width/(log10f(xRange.max)-log10f(xRange.min)); //log
	//y scale
	if (yRange.type == ATRLinear)
		ratio.y = plotarea.size.height/(yRange.max-yRange.min); //linear
	else if (yRange.type == ATRLogaritmic)
		ratio.y = plotarea.size.height/(log10f(yRange.max)-log10f(yRange.min)); //log
}
 

lazydog

macrumors 6502a
Sep 3, 2005
709
6
Cramlington, UK
If you want to check that x <= y then how about something like this:-

Code:
#define EPSILON ( 0.0001f )

float x, y ;
…
…
if ( ( x - y ) < EPSILON )
{
…
}

You would need to choose a suitable EPSILON though.

b e n
 

MrFusion

macrumors 6502a
Original poster
Jun 8, 2005
613
0
West-Europe
If you write a loop like

for (x = 0.0; x <= 1.5; x += 0.1) ...

you can never be sure how often it will be executed, because eventually x will have a value that is very close to 1.5, but it could be a tiny bit less, or a tiny bit more. Using double or long double instead of float won't help, x will be a lot closer to 1.5, but it will still be a tiny bit less or a tiny bit more. The solution is very simple: Use an integer variable.

int i;
double x;

for (i = 0; i <= 15; ++i) { x = i * 0.1; ... }

Nice idea.
Thanks gnasher and Lee.
 

MrFusion

macrumors 6502a
Original poster
Jun 8, 2005
613
0
West-Europe
If you want to check that x <= y then how about something like this:-

Code:
#define EPSILON ( 0.0001f )

float x, y ;
…
…
if ( ( x - y ) < EPSILON )
{
…
}

You would need to choose a suitable EPSILON though.

b e n

This also works, but since both 10E12 as 10E-9 is possible as value, choosing a suitable epsilon would be difficult.
 

lazydog

macrumors 6502a
Sep 3, 2005
709
6
Cramlington, UK
This also works, but since both 10E12 as 10E-9 is possible as value, choosing a suitable epsilon would be difficult.

I'm not sure what you mean. Are you saying your accumulated error could be as large as 1000000000000? :eek:

b e n

EDIT: Sorry I think i misunderstood you. Do you mean the range of values is 10E12 to 10E-9?
 

lazydog

macrumors 6502a
Sep 3, 2005
709
6
Cramlington, UK
Okay, I think this might work better for you then.…

Code:
#define EPSILON ( 0.0001f )

float x, y ;
…
…
if ( ( ( x - y ) < EPSILON * fabsf( x ) ) &&  ( ( x - y ) < EPSILON * fabsf( y ) ) )
{
…
}

b e n
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.