Register FAQ / Rules Forum Spy Search Today's Posts Mark Forums Read
Go Back   MacRumors Forums > Apple Systems and Services > Programming > Mac Programming

Reply
 
Thread Tools Search this Thread Display Modes
Old Mar 22, 2008, 01:54 PM   #1
MrFusion
macrumors 6502a
 
Join Date: Jun 2005
Location: West-Europe
aargh... floats are killing me

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)

Last edited by MrFusion; Mar 22, 2008 at 03:31 PM.
MrFusion is offline   0 Reply With Quote
Old Mar 22, 2008, 02:22 PM   #2
lee1210
macrumors 68040
 
lee1210's Avatar
 
Join Date: Jan 2005
Location: 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
lee1210 is offline   0 Reply With Quote
Old Mar 22, 2008, 02:24 PM   #3
MrFusion
Thread Starter
macrumors 6502a
 
Join Date: Jun 2005
Location: 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 is offline   0 Reply With Quote
Old Mar 22, 2008, 02:45 PM   #4
MDMstudios
macrumors member
 
Join Date: Mar 2008
Is there a way you can use a double value instead of a float value?
MDMstudios is offline   0 Reply With Quote
Old Mar 22, 2008, 02:52 PM   #5
MrFusion
Thread Starter
macrumors 6502a
 
Join Date: Jun 2005
Location: West-Europe
Quote:
Originally Posted by MDMstudios View Post
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.
MrFusion is offline   0 Reply With Quote
Old Mar 22, 2008, 02:55 PM   #6
MDMstudios
macrumors member
 
Join Date: Mar 2008
Quote:
Originally Posted by MrFusion View Post
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?

Last edited by MDMstudios; Mar 22, 2008 at 03:05 PM.
MDMstudios is offline   0 Reply With Quote
Old Mar 22, 2008, 03:09 PM   #7
MrFusion
Thread Starter
macrumors 6502a
 
Join Date: Jun 2005
Location: West-Europe
Quote:
Originally Posted by MDMstudios View Post
hmm, is there some type of method you can use for rounding?
roundf()
I mentioned it in my original post, but that also doesn't work well.
MrFusion is offline   0 Reply With Quote
Old Mar 22, 2008, 03:13 PM   #8
MDMstudios
macrumors member
 
Join Date: Mar 2008
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.

Last edited by MDMstudios; Mar 22, 2008 at 03:19 PM.
MDMstudios is offline   0 Reply With Quote
Old Mar 22, 2008, 03:22 PM   #9
MrFusion
Thread Starter
macrumors 6502a
 
Join Date: Jun 2005
Location: West-Europe
Quote:
Originally Posted by MDMstudios View Post
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.
MrFusion is offline   0 Reply With Quote
Old Mar 22, 2008, 03:26 PM   #10
MDMstudios
macrumors member
 
Join Date: Mar 2008
Quote:
Originally Posted by MrFusion View Post
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.
MDMstudios is offline   0 Reply With Quote
Old Mar 22, 2008, 03:30 PM   #11
lee1210
macrumors 68040
 
lee1210's Avatar
 
Join Date: Jan 2005
Location: 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.
lee1210 is offline   0 Reply With Quote
Old Mar 22, 2008, 03:46 PM   #12
gnasher729
macrumors G5
 
gnasher729's Avatar
 
Join Date: Nov 2005
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; ... }
gnasher729 is offline   0 Reply With Quote
Old Mar 22, 2008, 04:02 PM   #13
MrFusion
Thread Starter
macrumors 6502a
 
Join Date: Jun 2005
Location: West-Europe
Quote:
Originally Posted by lee1210 View Post
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
}
MrFusion is offline   0 Reply With Quote
Old Mar 22, 2008, 04:15 PM   #14
lazydog
macrumors 6502a
 
Join Date: Sep 2005
Location: Cramlington, UK
Send a message via MSN to lazydog
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
lazydog is offline   0 Reply With Quote
Old Mar 22, 2008, 04:28 PM   #15
MrFusion
Thread Starter
macrumors 6502a
 
Join Date: Jun 2005
Location: West-Europe
Quote:
Originally Posted by gnasher729 View Post
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 is offline   0 Reply With Quote
Old Mar 22, 2008, 04:31 PM   #16
MrFusion
Thread Starter
macrumors 6502a
 
Join Date: Jun 2005
Location: West-Europe
Quote:
Originally Posted by lazydog View Post
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.
MrFusion is offline   0 Reply With Quote
Old Mar 22, 2008, 04:35 PM   #17
MrFusion
Thread Starter
macrumors 6502a
 
Join Date: Jun 2005
Location: West-Europe
Quote:
Originally Posted by MrFusion View Post
Any help or advice for this idiot is welcome.
The idiot is grateful for all the replies.
MrFusion is offline   0 Reply With Quote
Old Mar 22, 2008, 04:37 PM   #18
lazydog
macrumors 6502a
 
Join Date: Sep 2005
Location: Cramlington, UK
Send a message via MSN to lazydog
Quote:
Originally Posted by MrFusion View Post
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?

b e n

EDIT: Sorry I think i misunderstood you. Do you mean the range of values is 10E12 to 10E-9?
lazydog is offline   0 Reply With Quote
Old Mar 22, 2008, 04:47 PM   #19
Nutter
macrumors 6502
 
Join Date: Mar 2005
Location: London, England
Have you looked at NSDecimalNumber?
Nutter is offline   0 Reply With Quote
Old Mar 22, 2008, 04:48 PM   #20
lazydog
macrumors 6502a
 
Join Date: Sep 2005
Location: Cramlington, UK
Send a message via MSN to lazydog
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
lazydog is offline   0 Reply With Quote

Reply
MacRumors Forums > Apple Systems and Services > Programming > Mac Programming

Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump

Similar Threads
thread Thread Starter Forum Replies Last Post
The new refurbs are killing me! Boe11 iMac 37 Aug 8, 2011 08:21 PM
Imac 27 I5. Help, my eyes are killing me! wayneholbrook iMac 3 Apr 26, 2010 01:05 PM
IPHONE and Tmobile Issues are killing me. bratjeannie Jailbreaks and iOS Hacks 2 Mar 6, 2009 02:03 AM
What ICC profile does FireFox use?! Colors are killing me. EstorilM Digital Photography 19 Feb 20, 2007 10:19 AM
These hours are killing me!! Taft Community 10 Aug 20, 2002 01:04 PM


All times are GMT -5. The time now is 03:50 AM.

Mac Rumors | Mac | iPhone | iPhone Game Reviews | iPhone Apps

Mobile Version | Fixed | Fluid | Fluid HD
Copyright 2002-2013, MacRumors.com, LLC