Arbitrary-precision decimal arithmetic for PHP 7
This library provides a PHP extension that adds support for correctly-rounded, arbitrary-precision decimal floating point arithmetic. Applications that rely on accurate
numbers [ie. money, measurements, or mathematics] can use Decimal
instead of float
or string
to represent numerical values.
The implementation uses the same arbitrary precision library as Python’s decimal, called mpdecimal.
The decimal extension offers several advantages over the float
data type:
- All rational numbers can be
represented accurately. In contrast, numbers like
0.2
do not have exact representations in binary floating point. You can read more about this in the floating point guide or in PHP’s documentation. - While a binary floating point value like
0.2
comes close, the small difference prevents reliable equality testing, and inaccuracies may accumulate over time. - Arbitrary precision allows for numbers that are not bound by the same upper and lower limits as
float
- numbers can be as big or small as required. - PHP does a good job of hiding the inaccuracies of binary floating point representation with the precision INI setting. By default,
0.1 + 0.2
will have a string value of0.3
even though the internal Cdouble
can not represent the result accurately. For example:
var_dump[0.1 + 0.2]; // float[0.3]
var_dump[0.1 + 0.2 - 0.3]; // float[5.5511151231258E-17]
PHP already has arbitrary precision math functions…
The current goto answer for arbitrary precision math in PHP is bcmath. However, the Decimal
class offers multiple advantages over bcmath:
- Decimal values are objects, so you can typehint
Decimal
instead ofstring
. - Arithmetic and comparison operators are supported.
- Precision is defined as the number of significant figures, and scale is the number of digits behind the decimal point. This means that a number like
1.23E-1000
would require a scale of1002
but a precision of3
. This library uses precision; bcmath uses scale. - Scientific notation is supported, so you can use strings like
"1.23E-1000"
to construct aDecimal
. At the time of this writing, you can not do this with bcmath. - Calculations are significantly faster. See #performance for some benchmarks.
Installation
Dependencies
- PHP 7
- libmpdec 2.4+
Composer
Composer can not be used to install the extension. The php-decimal/php-decimal
package can be used to
specify the extension as a dependency and provides stubs for IDE integration. If you are using Composer and would like to add this extension as a dependency, require php-decimal/php-decimal
.
Install
The easiest way to install the extension is to use PECL:
If you are using phpbrew:
phpbrew ext install decimal
phpbrew ext enable decimal
Enable
Remember to enable to extension in your .ini file.
extension=decimal.so # Unix, OS X
extension=php_decimal.dll # Windows
Verify
You can confirm that the extension is installed with php --re decimal
.
Basic Usage
The Decimal
class is under the Decimal
namespace.
Decimal
objects can be constructed using a Decimal
, string
, or int
value, and an optional precision which defaults to 28.
Special float
values are also supported [NAN
, INF
and -INF
], but float
is otherwise not a valid argument in order to avoid accidentially
using a float
. If you absolutely must use a float
to construct a decimal you can cast it to a string
first, but doing so if affected by the precision INI setting.
Projects using this extension should avoid float
entirely, wherever possible. An example workflow is to store values as DECIMAL
in the database, query them as string
, parse to Decimal
, perform calculations, and finally prepare for the
database using toFixed.
JSON conversions will automatically convert the decimal to string
using all signficant figures.
A warning will be raised if value was not parsed completely. For example, "0.135"
to a precision of 2
will result in "0.14"
with a warning. Similarly, 123
with a precision of 2
would result in 120
with a warning because data has been lost.
Decimal
is final and
immutable. Arithmetic operations always return a new Decimal
using the maximum precision of the object and the operands. The result is therefore accurate up to MAX[$this->precision[], $op1->precision[], ...]
significant figures, subject to rounding of the last digit.
For example:
use Decimal\Decimal;
$a = new Decimal["1", 2];
$b = new Decimal["7", 8];
print_r[$a / $b];
Decimal\Decimal Object
[
[value] => 0.14285714
[precision] => 8
]
Scalar operands inherit the precision of the Decimal
operand, which avoids the need to construct a new object for the operation. If a scalar operand must be parsed with a higher precision, you should construct a new
Decimal
with an explicit precision. The result of a decimal operation is always a Decimal
.
For example:
use Decimal\Decimal;
$op1 = new Decimal["0.1", 4];
$op2 = "0.123456789";
print_r[$op1 + $op2];
use Decimal\Decimal;
/**
* @param int $n The factorial to calculate, ie. $n!
* @param int $p The precision to calculate the factorial to.
*
* @return Decimal
*/
function factorial[int $n, int $p = Decimal::DEFAULT_PRECISION]: Decimal
{
return $n 0.2235
[precision] => 4
]
Sandbox
This is a limited environment where you can experiment with Decimal
.