2024年PHP开发者大会日本站

fmod

(PHP 4 >= 4.2.0, PHP 5, PHP 7, PHP 8)

fmod返回参数相除的浮点余数(模)

描述

fmod(float $num1, float $num2): float

返回被除数 (num1) 除以除数 (num2) 的浮点余数。余数 (r) 定义为:num1 = i * num2 + r,其中 i 为某个整数。如果 num2 非零,则 rnum1 符号相同,且大小小于 num2 的大小。

参数

num1

被除数

num2

除数

返回值

num1/num2 的浮点余数

范例

示例 #1 使用 fmod()

<?php
$x
= 5.7;
$y = 1.3;
$r = fmod($x, $y);
// $r 等于 0.5,因为 4 * 1.3 + 0.5 = 5.7
?>

参见

  • / - 浮点除法
  • % - 整数模
  • intdiv() - 整数除法 - 整数除法

添加笔记

用户贡献笔记 20 条笔记

4
nospam at neonit dot de
7 年前
请注意,由于缺乏修复浮点表示错误的功能,fmod 的行为与用 PHP 本身编写的类似函数的行为不同。

看看这个
<?php
var_dump
(10 / (10 / 3) === 3.0); // bool(true)
var_dump(fmod(10, 10 / 3)); // float(3.3333333333333)
var_dump(fmod(10, 10 / 3) < 10 / 3); // bool(true)
?>

内部无法精确表示 10 / 3 的结果,因此它总是略高于或略低于实际结果。在本例中,该示例证明它略高于实际结果。

PHP 似乎非常擅长自动修复浮点表示错误,因此它们的行为符合用户的预期。这就是第一行产生 true 的原因,尽管结果略小于 3(例如 2.9999999999[某些值])。我未能诱使 PHP 将结果舍入或裁剪为 2。

但是,fmod 似乎在计算过程中不应用这些修复。从 10 / 3 中,它得到一个略小于 3 的值,将其向下取整为 2,然后返回 10 - 2 * 10 / 3,这略小于 10 / 3 的实际结果,但看起来像 10 / 3(第三行)。

不幸的是,这不是预期的结果。请参阅其他说明以获取高质量的修复。
11
jphansen at uga dot edu
19 年前
fmod() 不会镜像计算器的模函数。例如,fmod(.25, .05) 将返回 .05 而不是 0,因为使用了 floor()。使用上述示例,您可以通过在自定义 fmod() 中用 round() 替换 floor() 来获得 0。

<?
function fmod_round($x, $y) {
$i = round($x / $y);
return $x - $i * $y;
}

var_dump(fmod(.25, .05)); // float(0.05)
var_dump(fmod_round(.25, .05)); // float(0)
?>
2
timo underscore teichert at yahoo dot de
10 年前
此函数的行为似乎随着时间的推移而发生了变化。

<?php

echo fmod(3,5);
// php 5.3.2 输出 -2
// php 5.3.8 输出 3

echo fmod(2,5);
// php 5.3.2 输出 2
// php 5.3.8 输出 2

?>

- Timo
6
cory at lavacube dot net
18 年前
我不认为这是正确的。

使用你的补丁试试这个
<?php

echo duration( mktime(0, 0, 0, 1, 0, 2006)-time() );

?>

目前,这将显示
1 个月,22 天,24 小时,49 分钟,15 秒

这完全不正确。因为它现在是 12 月 9 日。

这里真正的缺陷在于“年”和“月”周期的计算方式。因为大多数月份的长度都不同……

非常感谢 SnakeEater251 指出这一点。

获得略微更准确结果的最快方法是使用基于一个“真实”年(即 365.25 天)的平均值。

将年份和月份更改为
'year' => 31557600, // 一个“真实年”(365.25 天)
'month' => 2629800, // 一个“真实年”除以 12 :-)

我将致力于开发一个真正的修复程序,以实现精确的准确性。;-)

- Cory Christison
3
dePijd
14 年前
此类经过多个单元测试,并修复了在 bugs.php.net 中发现的所有故障

<?php
抽象类 MyNumber {
public static function
isZero($number, $precision = 0.0000000001)
{
$precision = abs($precision);
return -
$precision < (float)$number && (float)$number < $precision;
}
public static function
isEqual($number1, $number2)
{
return
self::isZero($number1 - $number2);
}
public static function
fmod($number1, $number2)
{
$rest = fmod($number1, $number2);
if (
self::isEqual($rest, $number2)) {
return
0.0;
}
if (
mb_strpos($number1, ".") === false) {
$decimals1 = 0;
} else {
$decimals1 = mb_strlen($number1) - mb_strpos($number1, ".") - 1;
}
if (
mb_strpos($number2, ".") === false) {
$decimals2 = 0;
} else {
$decimals2 = mb_strlen($number2) - mb_strpos($number2, ".") - 1;
}
return (float)
round($rest, max($decimals1, $decimals2));
}
}
?>
1
[email protected]
6年前
不要依赖这个函数,例如

$a = "7.191"
$b = "2.397000"

if(fmod(floatval($a), floatval($b)) === 0.0) {
..
//结果为假,因为
//float(4.4408920985006E-16) != 0.0
2
KRAER
10 年前
为了基于PHP创建一个在bash中生成素数的列表,并且可以在中断后恢复,我使用了fmod()函数以及PHP评论区中其他两位用户提供的一些代码片段。

这将输出
"素数;最后一个素数与当前素数的差值"

所以功劳归于他们。我只做了日志文件的输出。

这个函数可以处理fmod()支持的任何最大值。只需输入$end值。并对日志文件执行touch命令,然后使用chmod 666命令,以便PHP可以访问它。

<?php
function tailCustom($filepath, $lines = 1, $adaptive = true) {

// 打开文件
$f = @fopen($filepath, "rb");
if (
$f === false) return false;

// 设置缓冲区大小
if (!$adaptive) $buffer = 4096;
else
$buffer = ($lines < 2 ? 64 : ($lines < 10 ? 512 : 4096));

// 跳转到最后一个字符
fseek($f, -1, SEEK_END);

// 读取并根据需要调整行号
// (否则,如果文件不以空行结尾,结果将不正确)
if (fread($f, 1) != "\n") $lines -= 1;

// 开始读取
$output = '';
$chunk = '';

// 当我们还需要更多行时
while (ftell($f) > 0 && $lines >= 0) {

// 计算应该回退多远
$seek = min(ftell($f), $buffer);

// 执行回退跳转(相对于当前位置)
fseek($f, -$seek, SEEK_CUR);

// 读取一部分并将其添加到输出
$output = ($chunk = fread($f, $seek)) . $output;

// 跳转回开始读取的位置
fseek($f, -mb_strlen($chunk, '8bit'), SEEK_CUR);

// 减少行计数器
$lines -= substr_count($chunk, "\n");

}

// 当行数过多时
// (由于缓冲区大小,我们可能读取了过多的行)
while ($lines++ < 0) {

// 找到第一个换行符并删除其之前的所有文本
$output = substr($output, strpos($output, "\n") + 1);

}

// 关闭文件并返回
fclose($f);
return
trim($output);

}

function
isPrime( $num )
{
for(
$i = 2; $i*$i <= $num; $i++ )
if( !
fmod($num,$i) )
return
FALSE;

return
TRUE;
}

$logfile = 'prim_save.log';

$lastline = explode(";", tailCustom($logfile));
$begin = ($lastline[0] +1);
$lastprime = $lastline[0];

$end = 999999999999999999999999999999999999;

$fp = fopen($logfile, 'a');
//行格式 $i.';'.$difference.';'."\n"

for($i = $begin; $i<$end; $i++)
{
if(
isPrime($i) == TRUE)
{
$difference = $i - $lastprime;
fputs($fp,$i.';'.$difference.';'."\n");
$lastprime = $i;
}
}

fclose($fp);
?>
2
dan danschafer net
6年前
警告:由于浮点数的工作方式,当输入$x和$y之间存在巨大的数量级差异,或者输入和输出值之间存在差异时,fmod()和任何简单的替代方法都存在问题。如果您需要处理大数或任意精度,最好使用BC Math或GMP之类的工具。

在处理fmod()的问题时,请记住floor()总是趋向于-INF,而不是0。这导致一个常用的fmod()替代方法只适用于正数。
<?php
function fmod_positive_only($x, $y) {
return
$x - floor($x/$y) * $y;
}
?>
给定这些简单的输入值
fmod_positive_only(-5, 3) = 1 (错误)
-5 % 3 = -2 (正确)

正确地去除商的小数部分可以通过强制转换为int(总是趋向于零)或动态选择ceil()或floor()来实现。为了保持精度而动态选择floor或ceil是矫枉过正。如果您的$x和$y值差异如此之大以至于在转换时出现溢出问题,那么它可能无论如何都会存在精度问题(参见下面的警告)。

<?php
function fmod_overkill($x, $y) {
if (!
$y) { return NAN; }
$q = $x / $y;
$f = ($q < 0 ? 'ceil' : 'floor');
return
$x - $f($q) * $y;
}
?>

这是在给定“正常”数字时fmod()的“最佳”替代方案。
<?php
function fmod_alt($x, $y) {
if (!
$y) { return NAN; }
return
floatval($x - intval($x / $y) * $y);
}
?>

警告:即使您得到非零响应,也要了解您的输入数字以及fmod()何时可能出错。对于大值或取决于您的输入变量类型,float可能仍然没有足够的精度来得到正确的答案。以下是fmod()及其替代方法的一些问题。

PHP_INT_MAX = 9223372036854775807
fmod(PHP_INT_MAX, 2) = 0 (错误)
fmod_alt(PHP_INT_MAX, 2) = 0 (错误)
PHP_INT_MAX % 2 = 1 (正确)

fmod(PHP_INT_MAX, PHP_INT_MAX - 1) = 0 (错误)
fmod_alt(PHP_INT_MAX, PHP_INT_MAX - 1) = 1 (正确)
fmod_alt(PHP_INT_MAX, PHP_INT_MAX - 1.0) = 0 (错误)
PHP_INT_MAX % (PHP_INT_MAX - 1) = 1 (正确)
PHP_INT_MAX % (PHP_INT_MAX - 1.0) = 9223372036854775807 (错误)

fmod(PHP_INT_MAX, 131) = 98 (错误)
fmod_alt(PHP_INT_MAX, 131) = 359 (错误)
fmod_positive_only(PHP_INT_MAX, 131) = 0 (错误)
PHP_INT_MAX % 131 = 97 (正确)
3
alex at xelam dot net
20年前
整数模运算

如果您想要两个整数而不是浮点数的除法余数,请使用“%”;例如

<?php
$a
= 4;
$b = 3;

print(
$a % $b);
?>

将输出“1”。
2
cory at simplesystems dot ca
19 年前
关于Ryan Means之前笔记的一个补充说明

与其使用explode()获取小数点前的数字,不如使用floor()……floor()向下取整,这正是需要的。

他使用floor()的相同示例:

<?PHP
$totalsec
=XXXXXXX; //用秒的整数替换X

$daysarray = floor( $totalsec/86400 );

$partdays = fmod($totalsec, 86400);
$hours = floor( $partdays/3600 );

$parthours = fmod($partdays, 3600);
$min = floor( $parthours/60 );

$sec = fmod($parthours, 60);

echo
"days " . $days . "<br>";
echo
"hours " . $hours . "<br>";
echo
"minutes " . $min . "<br>";
echo
"seconds " . $sec . "<br>";
?>
2
ysangkok at gmail dot com
17年前
请注意,这
<?php
function custom_modulo($var1, $var2) {
$tmp = $var1/$var2;

return (float) (
$var1 - ( ( (int) ($tmp) ) * $var2 ) );
}

$par1 = 1;
$par2 = 0.2;

echo
"fmod: ";
var_dump(fmod ( $par1 , $par2 ));
echo
"custom_modulo: ";
var_dump(custom_modulo ( $par1 , $par2 ));
?>

给出以下结果

fmod: float(0.2)
custom_modulo: float(0)

Fmod没有给出期望的结果,因此我做了自己的。
1
Adrien Gibrat
11年前
有一种优雅的方法可以计算最大公约数
https://en.wikipedia.org/wiki/Greatest_common_divisor

// 计算最大公约数的递归函数(欧几里得方法)
function gcd ($a, $b) {
return $b ? gcd($b, $a % $b) : $a;
}
// 然后简化任何整数列表
echo array_reduce(array(42, 56, 28), 'gcd'); // === 14

如果您想使用浮点数,请使用近似值

function fgcd ($a, $b) {
return $b > .01 ? fgcd($b, fmod($a, $b)) : $a; // 使用fmod
}
echo array_reduce(array(2.468, 3.7, 6.1699), 'fgcd'); // ~= 1.232

您可以在PHP 5.3中使用闭包

$gcd = function ($a, $b) use (&$gcd) { return $b ? $gcd($b, $a % $b) : $a; };
2
picaune at hotmail dot com
21年前
NAN(.net等效项为Double.NaN)表示“非数字”。
一些获得NaN的方法是模0和0的平方根。
1
verdy_p
11年前
请注意,fmod不等于此基本函数

<?php
function modulo($a, $b) {
return
$a - $b * floor($a / $b);
}
?>

因为fmod()将返回与$a符号相同的数值。换句话说,floor()函数是不正确的,因为它向-INF舍入而不是向零舍入。

要正确地模拟fmod($a, $b),方法是

<?php
function fmod($a, $b) {
return
$a - $b * (($b < 0) ? ceil($a / $b) : floor($a / $b)));
}
?>

请注意,如果$b为空,则这两个函数都会抛出除零异常。

上面的第一个函数modulo()是数学函数,它可用于处理循环结构(例如日历计算或三角函数)

- fmod($a, 2*PI) 如果$a为正,则返回[0..2*PI)范围内的值
- fmod($a, 2*PI) 如果$a为负,则返回[-2*PI..0]范围内的值
- modulo($a, 2*PI) 无论$a的符号如何,都始终返回[0..2*PI)范围内的值
1
matrebatre
16年前
我一直使用这个

函数 modulo($n,$b) {
return $n-$b*floor($n/$b);
}

它似乎工作正常。
1
SnakeEater251
19 年前
关于 cory at lavacube dot net 提供的代码的说明。
不使用 floor 而使用 round 会得到更好的结果。随着时间的推移,你会注意到输出的时间会有很大的偏差。

所以,不要使用 $temp = floor( $int_seconds / $length );
而是使用 $temp = round( $int_seconds / $length );

<?php

function duration( $int_seconds=0, $if_reached=null )
{
$key_suffix = 's';
$periods = array(
'year' => 31556926,
'month' => 2629743,
'day' => 86400,
'hour' => 3600,
'minute' => 60,
'second' => 1
);

// 用于隐藏较高期间的 0
$flag_hide_zero = true;

// 执行循环
foreach( $periods as $key => $length )
{
// 计算
$temp = round( $int_seconds / $length );

// 判断 temp 是否符合输出条件
if( !$flag_hide_zero || $temp > 0 )
{
// 存储到数组中
$build[] = $temp.' '.$key.($temp!=1?'s':null);

// 将标志设置为 false,允许较低期间显示 0
$flag_hide_zero = false;
}

// 获取剩余秒数
$int_seconds = fmod($int_seconds, $length);
}

// 返回输出结果,如果非空,则将其合并为字符串,否则输出 $if_reached
return ( !empty($build)?implode(', ', $build):$if_reached );
}

?>
1
linkboss at gmail dot com
15年前
你也可以使用取模运算符 '%',它返回相同的结果

<?php
$var1
= 5;
$var2 = 2;

echo
$var1 % $var2; //返回 1
echo fmod($var1,$var2); //返回相同的结果
?>
1
konstantin at rekk dot de
20年前
如果你需要将整数在零时归零,非零时归一,可以使用

$sign = (integer)(boolean)$integer;

代替

$sign = $integer > 0 ? 1 : 0;

它在 100 次操作中速度更快(至少在我的机器上是这样)。
1
cory at lavacube dot net
19 年前
生成持续时间字符串的更正式的方法

<?php

function duration( $int_seconds=0, $if_reached=null )
{
$key_suffix = 's';
$periods = array(
'year' => 31556926,
'month' => 2629743,
'day' => 86400,
'hour' => 3600,
'minute' => 60,
'second' => 1
);

// 用于隐藏较高期间的 0
$flag_hide_zero = true;

// 执行循环
foreach( $periods as $key => $length )
{
// 计算
$temp = floor( $int_seconds / $length );

// 判断 temp 是否符合输出条件
if( !$flag_hide_zero || $temp > 0 )
{
// 存储到数组中
$build[] = $temp.' '.$key.($temp!=1?'s':null);

// 将标志设置为 false,允许较低期间显示 0
$flag_hide_zero = false;
}

// 获取剩余秒数
$int_seconds = fmod($int_seconds, $length);
}

// 返回输出结果,如果非空,则将其合并为字符串,否则输出 $if_reached
return ( !empty($build)?implode(', ', $build):$if_reached );
}

?>

简单用法
<?php

echo duration( mktime(0, 0, 0, 1, 1, date('Y')+1) - time(), '如果持续时间已到,则输出一些花哨的消息...' );

?>

享受吧 :-)
0
rocan
19 年前
john at digitizelife dot com

我不确定你的评论如何应用于 fmod..

但是有一种更简单的处理这种情况的方法..

它被称为位域(位屏蔽)

例如:

/* 类别 */
二进制 十进制 类别
0001 - 1 - 蓝色
0010 - 2 - 红色
0100 - 4 - 绿色
1000 - 8 - 黄色

/* 权限 */
0010 - 2 - Bob
0101 - 5 - John
1011 - 11 - Steve
1111- 15 - Mary

要找出每个用户的权限,你只需要进行按位与运算

$steve_auth=11;

function get_perm($auth)
{
$cats["Blue"]=1;
$cats["Red"]=2;
$cats["Green"]=4;
$cats["Yellow"]=8;
$perms=array();
foreach($cats as $perm=>$catNum)
{
if($auth & $catNum)
$perms[$perm]=true;

}

return $perms;
}

print_r(get_perm($steve_auth));
/*
返回
数组
(
[Blue] => 1
[Red] => 1
[Yellow] => 1
)
*/

这比你的素数方法简单得多,事实上,你甚至不需要在任何测试中使用函数来测试用户的权限,你可以直接使用按位与运算符。

你可能想阅读以下内容:

http://en.wikipedia.org/wiki/Bitmask
http://uk2.php.net/manual/en/language.operators.bitwise.php
To Top