A form field containing an amount is text until the application defines its syntax and range. Casting it to float first accepts representations that may not belong in your contract and introduces an unnecessary binary floating-point conversion. For a two-decimal, nonnegative amount, validate the text and assemble integer cents directly.
Prerequisites and business rule
Use a maintained PHP 8 CLI release. This dependency-free example was executed on PHP 8.3.6. It accepts ASCII digits, one decimal point and exactly two fractional digits. The whole part is either zero or a nonzero digit followed by at most five more digits. Its explicit maximum is 999999.99. There is no currency inference, locale parsing, rounding or sign handling.
The business cap is intentional. It keeps the maximum result at 99,999,999 cents, within a signed 32-bit integer as well as the wider integers commonly used by 64-bit PHP builds. PHP can promote overflowing integer calculations to floats, so a strict syntax rule without a numeric range policy is incomplete. This example avoids that boundary by limiting the accepted digit count before conversion.
Complete example
Save as money.php.
<?php
declare(strict_types=1);
function cents(string $text): int {
// Explicit business cap: 999999.99; ASCII, nonnegative, two decimals.
if (preg_match('/\A(0|[1-9][0-9]{0,5})\.([0-9]{2})\z/', $text, $m) !== 1) {
throw new InvalidArgumentException('expected canonical amount');
}
return ((int)$m[1]) * 100 + (int)$m[2];
}
$cases = ['0.00' => 0, '0.01' => 1, '12.30' => 1230, '999999.99' => 99999999];
foreach ($cases as $text => $expected) {
if (cents($text) !== $expected) throw new RuntimeException('wrong conversion');
}
$bad = ['01.00', '1.2', '1e2', '-1.00', ' 1.00', "1.00\n", '1000000.00', '1,00'];
$rejected = 0;
foreach ($bad as $text) {
try { cents($text); }
catch (InvalidArgumentException $e) { $rejected++; }
}
if ($rejected !== count($bad)) throw new RuntimeException('accepted invalid amount');
echo '12.30 => ', cents('12.30'), " cents\n";
echo "valid=4 rejected=8\n";
Run and expected output
php money.php12.30 => 1230 cents
valid=4 rejected=8
How the conversion works
The pattern captures whole and fractional digits separately. Absolute start and end anchors reject trailing newlines as well as extra punctuation. After validation, integer conversion is applied only to those bounded digit captures. Multiplying the whole part by 100 and adding the fraction gives 1230 for 12.30. No decimal floating-point value is created along this path.
Four accepted cases check zero, one cent, an ordinary amount and the upper bound. Eight rejected cases cover a leading zero, a short fraction, exponent notation, a negative sign, whitespace, a trailing newline, an amount beyond the cap and a comma decimal separator. Exceptions are counted explicitly, so the checks run even when PHP's built-in assertions are disabled.
Edge cases and troubleshooting
The function intentionally rejects 1.2 rather than silently treating it as 1.20. If your user interface accepts that shorthand, define a separate normalization step and test it; do not broaden the storage contract accidentally. Similarly, adding negative amounts requires an explicit sign policy and overflow review. A refund field and a sale amount field need not share the same grammar.
Store or transmit the returned integer with its currency and scale defined by the application. Two decimals are a rule of this example, not a universal fact about every currency. If an integration insists on a decimal string, preserve the canonical original or format from integer cents rather than converting through float. The local tests cover parsing and conversion only; no payment, database column or accounting system was contacted. Strict types apply at PHP call boundaries according to the calling file, so keep external type validation alongside the parser when integrating it into a larger application.
Primary references
Continue with PHP tutorials.