PHP Conference Japan 2024

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 不為零,r 的正負號與 num1 相同,且其絕對值小於 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 則筆記

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(第三行)。

不幸的是,這不是預期的結果。請參閱其他筆記以獲得高質量的修正。
jphansen at uga dot edu
19 年前
fmod() 的行為與計算機的 mod 函數不同。例如,由於 floor() 的關係,fmod(.25, .05) 將返回 .05 而不是 0。使用上述範例,您可以透過在自定義 fmod() 中用 round() 取代 floor() 來獲得 0。

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

var_dump(fmod(.25, .05)); // 浮點數(0.05)
var_dump(fmod_round(.25, .05)); // 浮點數(0)
?>
timo_teichert@yahoo.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
cory@lavacube.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
dePijd
14 年前
這個類別通過了幾個單元測試,並修復了在 bugs.php.net 中發現的所有錯誤。

<?php
abstract class 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));
}
}
?>
radoslaw dot roszkowski at gmail dot com
6 年前
不要依賴這個函式,例如:

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

if(fmod(floatval($a), floatval($b)) === 0.0) {
..
// 結果是 false,因為
//float(4.4408920985006E-16) != 0.0
KRAER
10 年前
為了在基於 bash 的 php 中創建一個質數列表,以便在中斷後可以繼續執行,我使用了 fmod() 以及其他兩位用戶在此處 php 評論中提供的一些程式碼片段。

這將輸出
「質數;最後一個質數和目前質數之間的差」

所以功勞歸於他們。我只做了日誌文件輸出。

這將適用於 fmod 支援的任何最大值。只需輸入 $end 值。並 touch 建立日誌文件,然後使用 chmod 666 讓 php 可以訪問它。

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

// Open file
$f = @fopen($filepath, "rb");
if (
$f === false) return false;

// Sets buffer size
if (!$adaptive) $buffer = 4096;
else
$buffer = ($lines < 2 ? 64 : ($lines < 10 ? 512 : 4096));

// Jump to last character
fseek($f, -1, SEEK_END);

// Read it and adjust line number if necessary
// (Otherwise the result would be wrong if file doesn't end with a blank line)
if (fread($f, 1) != "\n") $lines -= 1;

// Start reading
$output = '';
$chunk = '';

// While we would like more
while (ftell($f) > 0 && $lines >= 0) {

// Figure out how far back we should jump
$seek = min(ftell($f), $buffer);

// Do the jump (backwards, relative to where we are)
fseek($f, -$seek, SEEK_CUR);

// Read a chunk and prepend it to our output
$output = ($chunk = fread($f, $seek)) . $output;

// Jump back to where we started reading
fseek($f, -mb_strlen($chunk, '8bit'), SEEK_CUR);

// Decrease our line counter
$lines -= substr_count($chunk, "\n");

}

// While we have too many lines
// (Because of buffer size we might have read too many)
while ($lines++ < 0) {

// Find first newline and remove all text before that
$output = substr($output, strpos($output, "\n") + 1);

}

// Close file and return
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');
//Lineformat $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);
?>
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 (正確)

正確移除商的小數部分可以透過將其轉換為整數(永遠向零取整)或動態選擇 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() 何時可能出錯。對於很大的值,或者取決於您的輸入變數類型,浮點數可能仍然沒有足夠的精度來返回正確的答案。以下是 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 (正確)
alex at xelam dot net
20 年前
整數模數運算

如果您想要兩個整數相除的餘數,而不是浮點數,請使用「%」;例如:

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

print(
$a % $b);
?>

將會輸出「1」。
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>";
?>
ysangkok at gmail dot com
17 年前
請注意…
<?php
函式 custom_modulo($var1, $var2) {
$tmp = $var1/$var2;

返回 (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: 浮點數(0.2)
custom_modulo: 浮點數(0)

Fmod 沒有提供預期的結果,所以我寫了自己的函式。
Adrien Gibrat
11 年前
有一個簡潔的方法可以計算最大公因數 (gcm)
https://en.wikipedia.org/wiki/Greatest_common_divisor

// 遞迴函式計算最大公因數 (歐幾里得算法)
函式 gcd ($a, $b) {
返回 $b ? gcd($b, $a % $b) : $a;
}
// 然後簡化任何整數列表
echo array_reduce(array(42, 56, 28), 'gcd'); // === 14

如果您想使用浮點數,請使用近似值

函式 fgcd ($a, $b) {
返回 $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 = 函式 ($a, $b) use (&$gcd) { 返回 $b ? $gcd($b, $a % $b) : $a; };
picaune at hotmail dot com
21 年前
NAN (.net 等效於 Double.NaN) 表示「非數值」。
取得 NaN 的一些方法是模 0 和 0 的平方根。
verdy_p
11 年前
請注意,fmod 並不等同於以下基本函式

<?php
函式 modulo($a, $b) {
返回
$a - $b * floor($a / $b);
}
?>

因為 fmod() 會傳回與 $a 相符正負號的值。換句話說,floor() 函式不正確,因為它向 -INF 四捨五入而不是向零四捨五入。

正確模擬 fmod($a, $b) 的方法是

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

請注意,如果 $b 為空值,這兩個函式都會拋出除以零的錯誤。

上面第一個函式 modulo() 是一個數學函式,可用於處理循環結構(例如日曆計算或三角函數)

- 如果 $a 為正數,fmod($a, 2*PI) 會傳回 [0..2*PI) 範圍內的值
- 如果 $a 為負數,fmod($a, 2*PI) 會傳回 [-2*PI..0] 範圍內的值
- 無論 $a 的正負號為何,modulo($a, 2*PI) 永遠會傳回 [0..2*PI) 範圍內的值
matrebatre
16 年前
我一直都使用這個

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

而且它似乎運作正常。
SnakeEater251
19 年前
關於 cory at lavacube dot net 提供的程式碼的注意事項。
使用 round 取代 floor 會得到更好的結果。隨著時間值的增加,您會注意到輸出的時間會出現很大的偏差。

因此,我們應該使用 $temp = round( $int_seconds / $length ); 來取代 $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 );
}

?>
linkboss at gmail dot com
15 年前
您也可以使用模數運算子「%」,它會返回相同的結果。

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

echo
$var1 % $var2; // 返回 1
echo fmod($var1,$var2); // 返回相同結果
?>
konstantin at rekk dot de
20 年前
如果您需要將一個整數簡化為:若為零則為零,若非零則為 1,您可以使用

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

來代替

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

這樣會更快(至少在我的機器上,執行 100 次操作時會更快)。
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(), '如果時間已到,則輸出一些花俏的訊息...' );

?>

盡情使用 :-)
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

要找出每個使用者的權限,你只需要執行一個位元 AND 運算

$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
)
*/

這比你的質數想法簡單多了,事實上,你甚至不需要在任何使用者權限測試中使用函式,你可以直接使用位元 AND 運算子。

你可能會想閱讀以下內容

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