rfc:is-representable-as-float-int

PHP RFC: Add "is_representable_as_float()" and "is_representable_as_int()" functions

  • Version: 1.0
  • Date: 2025-07-29
  • Author: Alexandre Daubois, alex.daubois@gmail.com
  • Status: Under Discussion
  • Implementation: TBD

Introduction

This RFC proposes the addition of two new functions to PHP's standard library: is_representable_as_float() and is_representable_as_int().

These functions provide developers with a standardized way to check whether values can be losslessly converted between integer and floating-point representations. Among other things, this is particularly useful for:

  • Ensuring data integrity when performing arithmetic operations ;
  • Parsing input data of unknown type ;
  • Validating data before storage or transmission.

Here is an example of a real use case: when serializing data, it is currently challenging to determine whether a numeric value can be safely represented as a float or an integer without loss of precision:

<?php
 
// 9007199254740993 is NOT representable as a float
 
// currently, we can use is_numeric() to check if a value is numeric
// but this does not guarantee that the value can be represented as a float or an int
$data = ['price' => 9007199254740993];
if (/* how do I make sure the price can be represented in JSON? */) {
    // handle error, e.g., log it or throw an exception
    // or cast to string
}
 
// with this RFC
$data = ['price' => 9007199254740993];
if (!is_representable_as_float($data['price'])) {
    $data['price'] = (string) $data['price'];
}
?>

Proposal

Add the following two functions:

  • function is_representable_as_float(mixed $value): bool
  • function is_representable_as_int(mixed $value): bool

When does is_representable_as_float() return true?

  • The value is a float that is not NaN or infinity
  • The value is an integer that can be converted to a float without loss of precision
  • The value is a string that represents a valid floating-point number (e.g., “3.14”, “1e10”, “-0.001”)
  • The value is a string that represents a valid integer (e.g., “42”, “-7”) and can be converted to a float without loss of precision

About the loss of precision: it is important to know that IEEE 754 floating-point numbers can represent integers exactly up to 2^53 - 1 (inclusive) because the mantissa has 52 bits of precision plus an implicit leading bit. Therefore, any integer within this range can be represented as a float without loss of precision. An integer outside this range could lose precision when converted to a float. This is called the “safe integer range” for floats. MDN provides a good overview of this topic[1]. Because of this, the function return value is also platform-dependent:

is_representable_as_float(PHP_INT_MAX); // true on 64-bit platforms, false on 32-bit platforms

As said earlier, IEEE 754 floating-point numbers can represent all consecutive integers exactly up to 2^53 - 1 (inclusive). Beyond this range, only specific integers can be represented exactly, following a pattern based on the available mantissa bits:

  • From 2^53 to 2^54: only even numbers (multiples of 2) ;
  • From 2^54 to 2^55: only multiples of 4 ;
  • From 2^55 to 2^56: only multiples of 8 ;
  • And so on...

Additionally, pure powers of 2 (2^0, 2^1, 2^2, ..., up to 2^1023) can always be represented exactly because they require only a single bit in the mantissa.

The function returns true for any value that can be exactly represented as a float, including these special cases.

<?php
 
is_representable_as_float(2**53);     // true
is_representable_as_float(2**53 + 1); // false, precision loss when cast to float
is_representable_as_float(42);        // true
is_representable_as_float("123.456"); // true
is_representable_as_float("1e308");   // false, not exactly representable as float
is_representable_as_float("1e400");   // false
 
// beyond safe range
is_representable_as_float(2**54);     // true (exact power of 2)
is_representable_as_float(2**54 + 2); // true (multiple of 2 in this range)
is_representable_as_float(2**54 + 1); // false (odd number, not representable)
 
// large pure powers of 2
is_representable_as_float(2**200);    // true (exact power of 2)
is_representable_as_float(2**1023);   // true (largest normal float)
is_representable_as_float(2**1024);   // false (overflow to infinity)
 
?>

When does is_representable_as_int() return true?

  • The value is an integer ;
  • The value is a float not greater than PHP_INT_MAX and not less than PHP_INT_MIN, without fractional part ;
  • The value is a string that represents a valid integer (e.g., “42”, “-7”) and can be converted to an integer within the range of PHP_INT_MIN and PHP_INT_MAX.

Here also, the function return value is platform-dependent:

is_representable_as_int(2**31); // true on 64-bit platforms, false on 32-bit platforms

More examples of the function return values:

is_representable_as_int(42);         // true
is_representable_as_int(3.14);       // false, has a fractional part
is_representable_as_int("123");      // true
is_representable_as_int("123.456");  // false, has a fractional part
is_representable_as_int("1e10");     // true on 64-bit platforms, false on 32-bit platforms
is_representable_as_int("1e400");    // false, not exactly representable as int

Naming

It's still unsure if the current naming of functions are a prefect fit. Here are a few other propositions to name the new functions:

  • is_safe_float() / is_safe_int()
  • fits_float() / fits_int()
  • can_cast_to_float() / can_cast_to_int()

Examples

<?php
 
// validate before serialization
$data = ['price' => 9007199254740993];
if (!is_representable_as_float($data['price'])) {
    // handle error, e.g., log it or throw an exception
    // or cast to string!
    $data['price'] = (string) $data['price'];
}
 
// validate data from external API
function validateNumericInput($value) {
    if (is_numeric($value) && is_representable_as_int($value)) {
        return (int) $value;
    }
 
    // ...
}
 
// edge cases
is_representable_as_float(INF);        // false
is_representable_as_float(-INF);       // false
is_representable_as_float(NAN);        // false
is_representable_as_float("");         // false
is_representable_as_float(null);       // false
is_representable_as_float(false);      // false
is_representable_as_float(true);       // false
is_representable_as_float("  42  ");   // true, like "is_numeric()"
is_representable_as_float("\r42\n");   // true, like "is_numeric()"
is_representable_as_float("0x2A");     // false, like "is_numeric()"
is_representable_as_float("0b01110");  // false, like "is_numeric()"
is_representable_as_int(2.0);          // true
is_representable_as_int(-0.0);         // true
 
class MyClass {
    public function __toString() {
        return "42";
    }
}
 
is_representable_as_int(new MyClass()); // true because of __toString()
is_representable_as_float(new MyClass()); // true as well
 
?>

Note: these functions use locale-independent parsing, similar to is_numeric(). Decimal separator is always “.” regardless of locale settings.

Backward Incompatible Changes

Existing code that declares a function with the same name will result in an error. However, this is unlikely to happen. A quick search on GitHub with the function names shows no results.

Proposed PHP Version(s)

Next minor version, likely PHP 8.6

Voting Choices

Implement
Real name Yes No
Final result: 0 0
This poll has been closed.

Patches and Tests

I'll take care of the implementation for this RFC.

References

rfc/is-representable-as-float-int.txt · Last modified: by alexandre-daubois