dev.club — where best developers and top companies connect.

dev.club — where best developers and top companies connect.Invite only

Request invite

Brick\Math

A PHP library to work with arbitrary-precision numbers.

Build Status Coverage Status Latest Stable Version Total Downloads License

Introduction

This library provides immutable classes to work with three types of numbers:

It automatically uses GMP or BCMath when available, and falls back to a pure-PHP implementation otherwise.

All classes work with a virtually unlimited number of digits, and are only limited by available memory and CPU time.

Installation

This library is installable via Composer:

composer require brick/math

Requirements

This library requires PHP 8.2 or later.

Although the library can work seamlessly on any PHP installation, it is highly recommended that you install the GMP or BCMath extension to speed up calculations. The fastest available calculator implementation will be automatically selected at runtime.

Number classes

The three number classes all extend the same BigNumber class:

Brick\Math\BigNumber
           ├── BigInteger
           ├── BigDecimal
           └── BigRational

BigNumber is an abstract class that defines the common behaviour of all number classes:

Instantiation

The constructors of the classes are not public, so you must use a factory method to obtain an instance.

All classes provide an of() factory method that accepts any of the following types:

Example:

BigInteger::of(123456);
BigInteger::of('9999999999999999999999999999999999999999999');

BigDecimal::of('9.99999999999999999999999999999999999999999999');
BigDecimal::of('1.23e1000');

BigRational::of('2/3');

The of() method of each class accepts all the representations above, as long as the value can be safely converted to that class:

BigInteger::of('1e3'); // 1000
BigInteger::of('1.00'); // 1
BigInteger::of('1.01'); // RoundingNecessaryException

BigDecimal::of('1/8'); // 0.125
BigDecimal::of('1/3'); // RoundingNecessaryException

BigRational::of('1.1'); // 11/10
BigRational::of('1.15'); // 23/20

[!NOTE] The of() factory method does not accept float values, because casting a float to string can be lossy. To convert a float to a BigDecimal, use one of the dedicated methods:

// Exact IEEE-754 representation — the value the float actually holds:
BigDecimal::fromFloatExact(0.1); // 0.1000000000000000055511151231257827021181583404541015625

// Shortest decimal that round-trips back to the same float:
BigDecimal::fromFloatShortest(0.1); // 0.1

Parsing untrusted input

of() places no hard limits on its input: a string with millions of digits is accepted as is, and a number in exponential notation is expanded to its full length, so a string as short as 1e1000000000 yields a number with a billion digits.

If your input comes from an untrusted source, such as an HTTP request, use parse() instead, which requires you to specify the allowed syntax and a maximum number of digits:

use Brick\Math\NumberSyntax;

BigDecimal::parse($input, allowedSyntax: NumberSyntax::DECIMAL, maxDigits: 20);

The $allowedSyntax parameter restricts the accepted notations. Plain integers such as 123 are always accepted, and each NumberSyntax case (DecimalPoint, Exponent, Fraction) allows one additional feature. The enum also provides constants for the most common combinations:

The $maxDigits parameter limits the number of digits, counted both as written in the input and in the resulting number, so that a value such as 1e1000000000 is rejected before it is ever expanded:

BigDecimal::parse('123.45', allowedSyntax: NumberSyntax::DECIMAL, maxDigits: 20); // 123.45
BigDecimal::parse('1.2e3', allowedSyntax: NumberSyntax::DECIMAL, maxDigits: 20); // NumberFormatException (exponent not allowed)
BigDecimal::parse('1e1000000000', allowedSyntax: NumberSyntax::SCIENTIFIC, maxDigits: 20); // NumberFormatException (too many digits)

Parameter types

All methods that accept a number (plus(), minus(), multipliedBy(), etc.) accept the same types as of(). For example, given the following number:

$integer = BigInteger::of(123);

The following lines are equivalent:

$integer->multipliedBy(123);
$integer->multipliedBy('123');
$integer->multipliedBy($integer);

As with of(), other types of numbers are accepted, as long as they can be safely converted to the current type:

echo BigInteger::of(2)->multipliedBy('2.0'); // 4
echo BigInteger::of(2)->multipliedBy('2.5'); // RoundingNecessaryException
echo BigDecimal::of('2.5')->multipliedBy(2); // 5.0

These parameters are converted with of(), so the same rules apply: for untrusted strings, use parse() first, and pass the resulting number to the method.

Immutability & chaining

The BigInteger, BigDecimal and BigRational classes are immutable: their value never changes, so that they can be safely passed around. All methods that return a BigInteger, BigDecimal or BigRational return a new object, leaving the original object unaffected:

$ten = BigInteger::of(10);

echo $ten->plus(5); // 15
echo $ten->multipliedBy(3); // 30

The methods can be chained for better readability:

echo BigInteger::of(10)->plus(5)->multipliedBy(3); // 45

Rounding

Unless documented otherwise, all methods either return an exact result or throw an exception if the result is not exact. Where applicable, this behaviour is configurable through an optional RoundingMode parameter:

Rounding mode Description
RoundingMode::Unnecessary Requires an exact result; throws if rounding would be needed.
RoundingMode::Up Rounds away from zero.
RoundingMode::Down Rounds toward zero.
RoundingMode::Ceiling Rounds toward positive infinity.
RoundingMode::Floor Rounds toward negative infinity.
RoundingMode::HalfUp Rounds to nearest; ties away from zero.
RoundingMode::HalfDown Rounds to nearest; ties toward zero.
RoundingMode::HalfCeiling Rounds to nearest; ties toward positive infinity.
RoundingMode::HalfFloor Rounds to nearest; ties toward negative infinity.
RoundingMode::HalfEven Rounds to nearest; ties to the even neighbor.
RoundingMode::HalfOdd Rounds to nearest; ties to the odd neighbor.

See the Division section for examples of RoundingMode in action.

[!TIP] PHP 8.4 introduced a native RoundingMode enum, used by round(), bcround() and BcMath\Number::round(). If you already have a native rounding mode at hand, you can convert it to its Brick\Math equivalent:

$roundingMode = RoundingMode::fromNativeRoundingMode(\RoundingMode::HalfAwayFromZero);

Arithmetic operations

Addition, subtraction and multiplication

These operations are straightforward on all number classes:

echo BigInteger::of(1)->plus(2)->multipliedBy(3); // 9
echo BigDecimal::of('1.2')->plus('3.4')->multipliedBy('5.6'); // 25.76
echo BigRational::of('2/3')->plus('5/6')->multipliedBy('5/4'); // 15/8

The scale of BigDecimal operation results is predictable:

BigRational results are automatically reduced to lowest terms.

Division

Division uses a class-specific API because exactness and precision rules differ between integers, decimals, and rationals.

BigInteger

By default, dividing a BigInteger returns the exact result of the division, or throws an exception if the remainder of the division is not zero:

echo BigInteger::of(999)->dividedBy(3); // 333
echo BigInteger::of(1000)->dividedBy(3); // RoundingNecessaryException

You can pass an optional RoundingMode to round the result, if necessary:

echo BigInteger::of(1000)->dividedBy(3, RoundingMode::Down); // 333
echo BigInteger::of(1000)->dividedBy(3, RoundingMode::Up); // 334

You can also compute quotients and remainders:

echo BigInteger::of(1000)->quotient(3); // 333
echo BigInteger::of(1000)->remainder(3); // 1

You can also get both in one call:

[$quotient, $remainder] = BigInteger::of(1000)->quotientAndRemainder(3);

BigDecimal

Dividing a BigDecimal always requires a scale to be specified. If the exact result of the division does not fit in the given scale, a RoundingMode must be provided:

echo BigDecimal::of(1)->dividedBy('8', 3); // 0.125
echo BigDecimal::of(1)->dividedBy('8', 2); // RoundingNecessaryException
echo BigDecimal::of(1)->dividedBy('8', 2, RoundingMode::HalfDown); // 0.12
echo BigDecimal::of(1)->dividedBy('8', 2, RoundingMode::HalfUp); // 0.13

If you know that the division yields a finite number of decimal places, you can use dividedByExact(), which will automatically compute the required scale to fit the result, or throw an exception if the division yields an infinite repeating decimal:

echo BigDecimal::of(1)->dividedByExact(256); // 0.00390625
echo BigDecimal::of(1)->dividedByExact(11); // RoundingNecessaryException

BigRational

The result of the division of a BigRational can always be represented exactly:

echo BigRational::of('13/99')->dividedBy('7'); // 13/693
echo BigRational::of('13/99')->dividedBy('9/8'); // 104/891

BigRational results are automatically reduced to lowest terms.

Other arithmetic operations

In addition to plus(), minus(), multipliedBy(), and dividedBy(), the library provides:

Sign and comparison

All number classes share the same sign and comparison methods through BigNumber.

Sign methods

Use these methods to inspect the sign of a number:

For sign-related transformations, use:

Comparison methods

Comparison works across all number classes (BigInteger, BigDecimal, BigRational):

You can also use min(), max(), and clamp() to compare and bound values.

Type conversion

Conversion to other number classes

All classes provide the following methods:

toBigInteger() and toBigDecimal() either return an exact result, or throw a RoundingNecessaryException if the conversion is not exact. toBigRational() always returns an exact result.

You can also convert any number to a BigDecimal with a given scale, rounding the result if necessary:

echo BigRational::of('2/3')->toScale(5, RoundingMode::Up); // 0.66667

To convert any number to a BigInteger with rounding, use:

echo BigRational::of('10/3')->toScale(0, RoundingMode::Up)->toBigInteger(); // 4

Conversion to native numbers

All classes provide the following methods:

[!WARNING] toFloat() is the only method of the library that returns an approximation. Use it with caution.

Conversion to string

All number classes can be converted to string using either the toString() method or the (string) cast. For example, the following lines are equivalent:

echo BigInteger::of(123)->toString();
echo (string) BigInteger::of(123);

Different number classes produce different outputs. Note that a BigDecimal with a scale of zero and a BigRational with a denominator of one both print as plain digit strings:

echo BigInteger::of(-123)->toString(); // -123

echo BigDecimal::of('1.0')->toString(); // 1.0
echo BigDecimal::of('1')->toString(); // 1

echo BigRational::of('2/3')->toString(); // 2/3
echo BigRational::of('1/1')->toString(); // 1

All string outputs are parseable by the of() factory method. The following is guaranteed to work:

BigNumber::of($bigNumber->toString());

[!IMPORTANT] Because BigDecimal::toString() and BigRational::toString() can return whole numbers, these numbers will be parsed as BigInteger when using BigNumber::of(). If you want to retain the original type when reparsing numbers, be sure to use of() on the specific class: BigDecimal::of() or BigRational::of().

BigRational to decimal string

In addition to the standard rational representation such as 2/3, rational numbers can be represented as decimal numbers with a potentially repeating sequence of digits. You can use toRepeatingDecimalString() to get this representation:

BigRational::of('1/2')->toRepeatingDecimalString(); // 0.5
BigRational::of('2/3')->toRepeatingDecimalString(); // 0.(6)
BigRational::of('171/70')->toRepeatingDecimalString(); // 2.4(428571)

The part in parentheses is the repeating period, if any.

[!NOTE] The of() and parse() factory methods do not accept decimal strings with repeating periods.

[!WARNING] BigRational::toRepeatingDecimalString() is unbounded. The repeating period can be as large as denominator - 1, so large denominators can require a lot of memory and CPU time. Example: BigRational::of('1/100019')->toRepeatingDecimalString() has a repeating period of 100,018 digits.

Base conversion

BigInteger can parse and format numbers in different bases:

echo BigInteger::fromBase('ff', 16); // 255
echo BigInteger::of(255)->toBase(16); // ff

echo BigInteger::fromArbitraryBase('bab', 'ab'); // 5
echo BigInteger::of(5)->toArbitraryBase('ab'); // bab

You can also convert to and from byte strings using fromBytes() and toBytes().

Bitwise operations

BigInteger supports bitwise operations:

and bit shifting:

Bit-level inspection helpers are also available:

Random number generation

BigInteger provides factory methods for random integers:

Both methods use a secure random source by default and throw RandomSourceException if randomness cannot be obtained.

Exceptions

All exceptions thrown by this library implement the MathException interface. This means that you can safely catch all exceptions thrown by this library using a single catch clause:

use Brick\Math\BigInteger;
use Brick\Math\Exception\MathException;

try {
    $number = BigInteger::of(1)->dividedBy(3);
} catch (MathException $e) {
    // ...
}

If you need more granular control over the exceptions thrown, you can catch the specific exception classes documented in each method:

Serialization

BigInteger, BigDecimal and BigRational can be safely serialized on a machine and unserialized on another, even if these machines do not share the same set of PHP extensions.

For example, serializing on a machine with GMP support and unserializing on a machine that does not have this extension installed will still work as expected.

JSON

All number classes support serialization to JSON using the json_encode() function:

echo json_encode(BigInteger::of(123)); // "123"

Additional information

Release process

This library follows semantic versioning.

PHPStan extension

A third-party PHPStan extension is available for this library. It provides more specific throw type narrowing for brick/math methods, so that PHPStan can infer the exact exception classes thrown. Note that this extension is not maintained by the author of brick/math.

Join libs.tech

...and unlock some superpowers

GitHub

We won't share your data with anyone else.