====== 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: https://github.com/php/php-src/pull/19308 ===== 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: 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 * 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]. 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. **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. The function behavior depends on the platform's integer range: is_representable_as_int(2.0**31); // true on 64-bit, false on 32-bit platforms is_representable_as_int('2147483648'); // true on 64-bit, 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 ===== 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); // true is_representable_as_float(-INF); // true is_representable_as_float(NAN); // true 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 ===== * Yes * No ===== Patches and Tests ===== The pull request is available here: https://github.com/php/php-src/pull/19308 ===== References ===== [1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number#number_encoding