Question

Comparing CGFloat Floating-Point Values in Objective-C

cobjective-ciosfloating-pointfloating-accuracy

Question

In UIKit, CGFloat is used for resolution-independent coordinates. Is it safe to compare a view's frame.origin.x directly with 0?

if (theView.frame.origin.x == 0) {
    // Perform an important operation.
}

Because CGFloat is a floating-point type, can rounding cause a value that appears to be zero to fail this comparison? Does Objective-C handle floating-point precision internally for operators such as ==, <=, >=, <, and >?

Short Answer

You will learn how CGFloat floating-point comparisons work, when an exact comparison with zero is appropriate, and when to use a tolerance for values produced by calculations. You will also see reliable Objective-C patterns for comparing UIKit coordinates.

Concept

CGFloat is a floating-point type used by Core Graphics and UIKit for values such as positions, sizes, transforms, and drawing coordinates.

Floating-point numbers cannot represent every decimal value exactly. For example, a calculation that is mathematically expected to produce 0 can sometimes produce a tiny value such as 0.0000001 or -0.0000001 instead.

Objective-C does not automatically apply a tolerance when it evaluates these operators:

==  !=  <  >  <=  >=

They compare the actual stored floating-point values.

An exact comparison with zero is often safe when the value was explicitly assigned zero or came from an API that guarantees an exact zero:

view.frame = CGRectMake(0, 0, 100, 100);

if (view.frame.origin.x == 0) {
    // This is expected to be true.
}

However, if the coordinate was created by arithmetic, animation, scaling, division, transforms, or accumulated updates, do not assume it will be exactly zero. In that situation, compare it to a tolerance that makes sense for your UI.

Also note that UIKit coordinates are allowed to be fractional. A view at x = 0.5 is valid, especially on high-density displays or when transforms are involved.

Mental Model

Think of a floating-point number as a measuring tape with a limited number of markings. It can record many values very accurately, but it cannot mark every possible decimal distance exactly.

If you put an object directly at the wall marked 0, checking whether it is at 0 is reliable. But if you move it left and right repeatedly using measurements, it may finish extremely close to the wall without landing on the exact 0 mark.

A tolerance is like saying, “If the object is within half a pixel of the wall, treat it as being at the wall.”

Take Quiz

Syntax and Examples

Use an exact comparison when zero is a deliberate, known value:

CGFloat x = 0;

if (x == 0) {
    NSLog(@"Exactly at the left edge");
}

Use a tolerance when values may have come from calculations:

#import <math.h>

CGFloat x = calculatedX;
CGFloat tolerance = 0.01;

if (fabs(x) < tolerance) {
    NSLog(@"Close enough to zero");
}

fabs(x) returns the absolute distance from zero:

  • fabs(0.003) is 0.003
  • fabs(-0.003) is 0.003
  • fabs(0) is 0

To compare two CGFloat values, compare the distance between them:

#import <math.h>

static inline BOOL CGFloatsAreNearlyEqual(CGFloat a, CGFloat b, CGFloat tolerance) {
    return fabs(a - b) < tolerance;
}

if (CGFloatsAreNearlyEqual(theView.frame.origin.x, 0, 0.01)) {
    // Treat the view as being at x = 0.
}

Choose the tolerance based on the meaning of the value. For a screen position, a small number of points such as 0.01 may be suitable in some cases. For a layout decision, a tolerance such as 0.5 points may better match the idea of “visually at the edge.”

Step by Step Execution

Consider this calculation:

#import <math.h>

CGFloat start = 0.1;
CGFloat movedRight = start + 0.2;
CGFloat returned = movedRight - 0.3;

NSLog(@"%.17g", (double)returned);

if (returned == 0) {
    NSLog(@"Exactly zero");
}

if (fabs(returned) < 0.000001) {
    NSLog(@"Effectively zero");
}

What happens:

  1. start stores an approximation of decimal 0.1.
  2. Adding 0.2 performs floating-point arithmetic on approximations.
  3. Subtracting 0.3 may leave a very small residual value rather than exact 0.
  4. returned == 0 checks whether the stored bits represent exactly zero. It can be false.
  5. fabs(returned) < 0.000001 checks whether the residual is small enough to be irrelevant for this task. It can be true.

The exact printed value and behavior can vary by architecture because CGFloat is typically a double on modern 64-bit Apple platforms and was historically a float on 32-bit platforms. The important rule is unchanged: arithmetic can introduce rounding.

Real World Use Cases

Floating-point comparisons appear frequently in UIKit and Core Graphics code:

  • Animation completion checks: Determine whether an animated position has reached a target position. Prefer the animation completion callback when available; otherwise use a tolerance for calculated positions.
  • Custom drawing: Treat a line as horizontal when its two y-values are sufficiently close.
  • Gesture handling: Decide whether a drag has moved far enough to count as movement.
  • Layout rules: Detect whether a view is visually at a container edge.
  • Zoom and transforms: Compare calculated scale values with an expected scale such as 1.0.
  • Geometry calculations: Detect nearly zero widths, heights, distances, or velocities before dividing by them.

For values representing pixels or points, the best tolerance is usually a product requirement, not a language constant. Ask: how close must these values be before the difference is invisible or unimportant?

Take Quiz

Real Codebase Usage

In production code, developers usually avoid scattering raw floating-point comparisons throughout view code. Instead, they use clear intent-based helpers and layout APIs.

Validate values before using them

#import <math.h>

CGFloat width = CGRectGetWidth(view.bounds);
if (!isfinite(width) || width <= 0) {
    return;
}

CGFloat scale = 200.0 / width;

This guard clause prevents invalid calculations when a value is not finite or cannot be used as a divisor.

Use a named comparison helper

#import <math.h>

static inline BOOL IsNearlyZero(CGFloat value, CGFloat tolerance) {
    return fabs(value) < tolerance;
}

if (IsNearlyZero(view.frame.origin.x, 0.5)) {
    // The view is visually at the leading edge.
}

A named helper makes the business decision visible: this is not exact mathematical equality; it is a UI-specific definition of “at the edge.”

Prefer constraints for layout state

When Auto Layout controls placement, checking frame.origin.x is often the wrong source of truth. Store and inspect the relevant constraint instead:

if (leadingConstraint.constant == 0) {
    // The layout rule has no leading offset.
}

If that constant is calculated, apply a tolerance there as well.

Use framework callbacks for completion

For animations, prefer APIs that report completion rather than polling a frame value:

[UIView animateWithDuration:0.25 animations:^{
    self.panelView.frame = CGRectMake(0, 0, 200, 200);
} completion:^(BOOL finished) {
    if (finished) {
        // The animation completed normally.
    }
}];

Common Mistakes

Assuming displayed output is the stored value

A debugger or log may round a tiny value and display it as 0.00. That does not mean the stored value is exactly zero.

CGFloat x = 0.00004;
NSLog(@"%.2f", (double)x); // Prints 0.00

if (x == 0) {
    // This does not run.
}

Use more precision while diagnosing a problem:

NSLog(@"%.17g", (double)x);

Using CGFLOAT_EPSILON as a general UI tolerance

CGFLOAT_EPSILON describes spacing near 1.0 for the underlying floating-point representation. It is usually far too small to represent a useful visual or layout tolerance.

// Usually not a meaningful UI rule.
if (fabs(x) < CGFLOAT_EPSILON) {
    // ...
}

Instead, choose a tolerance based on the coordinate system and the feature's requirement.

Using only an absolute tolerance for very large values

For values that may be very large, a fixed tolerance can be inappropriate. A relative comparison can be useful:

#import <math.h>

static inline BOOL CGFloatsAreNearlyEqualRelative(CGFloat a, CGFloat b, CGFloat tolerance) {
    CGFloat difference = fabs(a - b);
    CGFloat largest = MAX(fabs(a), fabs(b));
    return difference <= tolerance * MAX(1.0, largest);
}

For ordinary UIKit frame coordinates, a simple absolute tolerance is often easier and more appropriate.

Treating all comparisons as unsafe

This is unnecessarily cautious:

CGFloat x = 0;
if (fabs(x) < 0.01) {
    // ...
}

It is valid, but if your code explicitly assigned x = 0, x == 0 is clearer and correct.

Forgetting that NaN compares strangely

A not-a-number (NaN) value compares false with every ordered comparison, including equality:

CGFloat value = NAN;

if (value == value) {
    // Never runs for NaN.
}

Check isfinite(value) when a calculation might produce invalid values.

Comparisons

SituationRecommended approachWhy
Value was explicitly assigned 0value == 0Exact zero is exactly representable, and no calculation introduced rounding.
Value came from arithmeticfabs(value) < toleranceHandles tiny positive and negative rounding residuals.
Compare two calculated valuesfabs(a - b) < toleranceTests whether their difference is insignificant.
Check a lower or upper boundary from calculationsInclude a tolerance in the boundary rulePrevents tiny rounding differences from changing the decision.
Animation finishedCompletion callbackThe framework reports completion more reliably than frame polling.
Auto Layout stateInspect constraints or layout stateA frame is an output of layout, not always the intended rule.

<, >, <=, and >= are not inherently broken. They correctly compare the stored floating-point values. The issue is that the stored result may be slightly different from the mathematical value you expected.

For example, if a computed value should be zero but becomes -0.000001, then value < 0 is correctly true for that stored value. Whether your application should treat it as negative is a separate domain decision.

Take Quiz

Cheat Sheet

#import <math.h>

// Exact comparison: safe for a deliberately assigned zero.
if (x == 0) {
    // ...
}

// Is a calculated value close to zero?
if (fabs(x) < 0.01) {
    // ...
}

// Are two calculated values close?
if (fabs(a - b) < 0.01) {
    // ...
}

// Reject NaN and infinity before a critical calculation.
if (!isfinite(x)) {
    return;
}
  • CGFloat is floating-point; it may be double or float depending on the platform architecture.
  • Objective-C comparison operators do not automatically use a tolerance.
  • 0.0 is exactly representable in binary floating point.
  • -0.0 == 0.0 is true.
  • Use exact equality for known, deliberately assigned values.
  • Use a domain-appropriate tolerance for calculated values.
  • Do not use CGFLOAT_EPSILON as a default visual/layout tolerance.
  • NaN is not equal to itself; validate potentially invalid results with isfinite.

FAQ

Is CGFloat == 0 safe in Objective-C?

Yes, when the value was explicitly set to zero or is guaranteed by an API to be exact zero. It may be unreliable when the value is the result of floating-point arithmetic.

Does Objective-C fix floating-point comparison errors automatically?

No. Operators such as ==, <, and >= compare the stored values directly. Objective-C does not silently apply an epsilon or tolerance.

Can frame.origin.x be a fractional value?

Yes. UIKit uses points, and coordinates can be fractional due to scaling, transforms, animations, and layout calculations.

Should I always use an epsilon to compare CGFloat values?

No. Use exact comparison for deliberately assigned constants. Use a tolerance only when calculations can create rounding differences or when your requirement is inherently approximate.

What tolerance should I use for UIKit coordinates?

Choose one based on the UI behavior. For a visually meaningful edge check, 0.5 points may be reasonable in some interfaces. For a mathematical calculation, you may need a smaller value. There is no universal correct tolerance.

Is CGFLOAT_EPSILON the right tolerance for comparing view positions?

Usually not. It measures floating-point precision near 1.0, not the distance that is meaningful in your UI.

Are < and > unsafe for floating-point values?

They are technically correct comparisons of stored values. Be careful only when a computed value is expected to be on a boundary, because rounding can place it slightly above or below that boundary.

Take Quiz

Mini Project

Description

Build a small Objective-C utility that decides whether a draggable panel is at the left edge of its container. The panel's x-position may be computed during dragging, so the project uses a visual tolerance rather than exact equality.

Goal

Report whether a panel is at the left edge, near the left edge, or away from the edge using safe CGFloat comparisons.

Requirements

Requirement 1

Take Quiz

Keep learning

Related questions

View more