1

On iOS, I attempt to store a number 1448924191 as an NSNumber and then retrieve it as a float with floatValue:

    double max_float = MAXFLOAT; // really big num (3.4028234663852886E+38)
    double someEpoch = 1448924191;
    NSNumber *epochNum = [NSNumber numberWithDouble:someEpoch]; //

But when I ask for the float back, the return is off - rounded to the nearest 60-unit interval it seems:

        float asFloat = [epochNum floatValue];    // 1448924160 - rounded to 60???
        double asDouble = [epochNum doubleValue]; // 1448924191 - correct

Using doubleValue solves the issue, but why? Shouldn't float be able to handle this number since it is far less than FLOAT_MAX?

rmaddy
  • 314,917
  • 42
  • 532
  • 579
joel.d
  • 1,611
  • 16
  • 21

1 Answers1

1

As has been mentioned in the comments on your question, this is a fundamental limitation of floating-point numbers. A float on iOS is a 32-bit floating-point number. Some of those bits are used to store an exponent, so the number of significant digits that can be stored is substantially less than the number of digits in an int, for example.

If you #import <float.h>, you'll get access to a number of macros that will tell you the details of the floating-point format, including:

FLT_DIG

This is the number of decimal digits of precision for the float data type. Technically, if p and b are the precision and base (respectively) for the representation, then the decimal precision q is the maximum number of decimal digits such that any floating point number with q base 10 digits can be rounded to a floating point number with p base b digits and back again, without change to the q decimal digits.

Which is more-or-less the number of digits in the largest number that you can convert from int to float without losing any precision. It's actually a conservative estimate - you can calculate the absolute largest integer you can store in a float by doing some calculations with the other macros in that header, though I doubt that'll ever be something you'll want to do.

Speaking of converting from int to float, you should also be aware that in this line of code from your example:

double someEpoch = 1448924191;

You're implicitly converting from the integer 1448924191 to a double representation. This works fine, because 1448924191 is exactly-representable as both a double and an int. If you make that value larger than what will fit into a long int, you can get strange behavior, since the constant will get stored as an unsigned long, then converted to double. The compiler will at least warn you about this error when it occurs.

If you want a double constant, you should make sure to include a decimal point in the value:

double someEpoch = 1448924191.0;

This is less-critical if you're just going to assign it to a double variable immediately, but if you ever #define a float constant, it's useful to add the decimal point to ensure it gets treated as a floating-point value everywhere that it's used.

Mark Bessey
  • 19,598
  • 4
  • 47
  • 69