WARNING: Due to how floating point numbers work, fmod() and any simple alternatives are problematic when there is either a massive orders of magnitude different between the input $x and $y, or the input and output values. If you need to work with large numbers or arbitrary precision, it is best to work with something like BC Math or GMP.
When working around fmod()'s problems, remember that floor() always goes towards -INF, not 0. This causes a commonly proposed fmod() alternative to only work with positive numbers:
<?php
function fmod_positive_only($x, $y) {
return $x - floor($x/$y) * $y;
}
?>
Given these simplistic input values:
fmod_positive_only(-5, 3) = 1 (wrong)
-5 % 3 = -2 (correct)
Correctly removing the decimal part of the quotient can be achieved with either casting to an int (always goes towards zero) or dynamically choosing ceil() or floor(). Dynamically choosing floor or ceil in an attempt to keep precision is overkill. If your $x and $y values are so different that it suffers from an overflow problem when casting, it was probably going to have precision problems anyway (see warnings below).
<?php
function fmod_overkill($x, $y) {
if (!$y) { return NAN; }
$q = $x / $y;
$f = ($q < 0 ? 'ceil' : 'floor');
return $x - $f($q) * $y;
}
?>
This is the "best" alternative for fmod() when given "normal" numbers.
<?php
function fmod_alt($x, $y) {
if (!$y) { return NAN; }
return floatval($x - intval($x / $y) * $y);
}
?>
WARNING: Even when you get a non-zero response, know your input numbers and when fmod() can go wrong. For large values or depending on your input variable types, float still may not contain enough precision to get back the correct answer. Here are a few problems with fmod() and their alternatives.
PHP_INT_MAX = 9223372036854775807
fmod(PHP_INT_MAX, 2) = 0 (wrong)
fmod_alt(PHP_INT_MAX, 2) = 0 (wrong)
PHP_INT_MAX % 2 = 1 (correct)
fmod(PHP_INT_MAX, PHP_INT_MAX - 1) = 0 (wrong)
fmod_alt(PHP_INT_MAX, PHP_INT_MAX - 1) = 1 (correct)
fmod_alt(PHP_INT_MAX, PHP_INT_MAX - 1.0) = 0 (wrong)
PHP_INT_MAX % (PHP_INT_MAX - 1) = 1 (correct)
PHP_INT_MAX % (PHP_INT_MAX - 1.0) = 9223372036854775807 (wrong)
fmod(PHP_INT_MAX, 131) = 98 (wrong)
fmod_alt(PHP_INT_MAX, 131) = 359 (wrong)
fmod_positive_only(PHP_INT_MAX, 131) = 0 (wrong)
PHP_INT_MAX % 131 = 97 (correct)