📜 ⬆️ ⬇️

AzaMath - Number Systems (including custom) + arbitrary precision arithmetic in PHP

There was recently a problem associated with the conversion between different positional number systems.

As a real use, services are usually used to shorten URLs that use base36 / base62 systems or, for example, storing large numbers of huge numbers in the same base62 to save memory.

Searching among the existing solutions, I realized that none of them suits, and therefore, I decided to prepare a library component for various mathematical tasks in the framework.
')
It turned out AzaMath - a library for converting between number systems (including custom ones) + convenient arbitrary precision arithmetic.

Supports PSR-0 , easily put through the composer , 100% code coverage tests.

Number systems


The main task of the library, at the moment, is to transfer small, large and huge numbers from one positional number system to another. Support for standard systems with a base from 2 to 62, inclusive, as well as support for positional systems with its own alphabet (although all ASCII characters). Negative numbers and fractions are also desirable.

To begin with an analysis of alternative implementations that can be used.

Option 1 - Special functions (decbin, bindec, decoct, ...)

Six PHP functions for converting between the most common number systems (10, 2, 8, 16).
prosMinuses
  • Quickest way

  • Platform-dependent format of a negative number. Without using the '-' sign. It does not fit because we can have numbers of any size.
  • The maximum number supported is constant PHP_INT_MAX
  • Fractions are not supported.
  • Ignores incorrect characters in the original number, but only when converting to a decimal number. In the opposite direction is no longer working.
  • Limited number systems


Option 2 - base_convert

Standard PHP function, for converting between number systems from 2 to 36 inclusive.
prosMinuses
  • Ignore invalid characters in source

  • Negative numbers are not supported.
  • The maximum supported number is equal to the constant PHP_INT_MAX + 1
  • Fractions are not supported.
  • Limited number systems
  • 40–70% slower than special functions


Option 3 - GMP (gmp_strval (gmp_init ($ number, $ x), $ y))

In the presence of GMP extensions, you can use functions to change the number system from there. Systems from 2 to 62 and from -2 to -36 are supported (normal systems 2–36, only at the output of a letter in upper case).
prosMinuses
  • Negative numbers are supported normally.
  • No limit on the size of the number
  • All standard number systems are supported (2–62)

  • Fractions are not supported.
  • Conversion error with incorrect characters in the original number
  • 70–90% slower, base_convert
  • 70–215% slower than special functions

Its own conversion option was implemented completely independently of third-party extensions and without intermediate conversion into decimal systems, which allowed it to be as fast as possible in PHP (this is slow :) - about 6000% slower than the GMP implementation. Therefore, if possible, a faster implementation is always used. It is also highly recommended to install the GMP extension, if you suddenly need a lot and regularly convert numbers. Negative numbers are fully supported.

The library includes, as an example, three non-standard number systems: base32 and base64 with alphabets of known coding systems according to the RFC4648 standard, and also base64url from the same standard - base64 with a safe alphabet for use in the URL. Note that this is a conversion of numbers, not arbitrary strings, so the result of base64 will be completely different from the result of base64_encode .

Alas, the support of fractions has not finished yet - it was not possible to do it quickly enough and had to be postponed. Perhaps I will do it later, or if someone needs it, I will gladly accept a pull request with this feature.

Examples of using


$res = NumeralSystem::convert('WIKIPEDIA', 36, 10); echo $res . PHP_EOL; // 91730738691298 $res = NumeralSystem::convert('9173073869129891730738691298', 10, 16); echo $res . PHP_EOL; // 1da3c9f2dd3133d4ed04bce2 $res = NumeralSystem::convertTo('9173073869129891730738691298', 62); echo $res . PHP_EOL; // BvepB3yk4UBFhGew $res = NumeralSystem::convertFrom('BvepB3yk4UBFhGew', 62); echo $res . PHP_EOL; // 9173073869129891730738691298 

 //    c  . //       . //   ASCII . $alphabet = '!@#$%^&*()_+=-'; //  base14 $name = 'StrangeSystem'; NumeralSystem::setSystem($name, $alphabet); $number = '9999'; $res = NumeralSystem::convertTo($number, $name); echo $res . PHP_EOL; // $)!$ $res = NumeralSystem::convertFrom($res, $name); echo $res . PHP_EOL; // 9999 


Arithmetic of arbitrary precision


Handling over large numbers is done as a convenient wrapper over the BCMath extension. In the initial state, BCMath is not very convenient for real use. Just filling the result with zeros to the specified precision (1234.2130000000 ...) is worth something.

All basic arithmetic operations are supported, as well as exponentiation, square root, division residue, bit shift, rounding, comparisons, and some other operations.

All numbers inside are represented as strings, but int / float types are accepted as input. In tests, it quickly became clear how much accuracy is lost with the float data type. In addition, the exponential notation (12e26, 12e-26 ) is not supported at all at the BCMath level. So we had to look for the possibility of preserving the maximum available accuracy, so all the floating-point numbers at the input are processed by a special algorithm, keeping as accurate as possible.

Examples of using


 //        - 20 (  100) $number = new BigNumber('118059162071741130342591466421', 20); //  $number->divide(12345678910); echo $number . PHP_EOL; // 9562792207086578954.49764831288650451382 //          //    : // 1) HALF_UP -      *.5 ( ) // 2) HALF_DOWN -      *.5 // 3) CUT,     ,     //  ,    BigNumber::ROUND_*  //    PHP - PHP_ROUND_HALF_UP, PHP_ROUND_HALF_DOWN $number->divide(9876543210)->round(3, PHP_ROUND_HALF_DOWN); echo $number . PHP_EOL; // 968232710.955 

 //   $number = new BigNumber(10); echo ($number->compareTo(20) < 0) . PHP_EOL; // 1 echo $number->isLessThan(20) . PHP_EOL; // 1 $number = new BigNumber(20); echo ($number->compareTo(10) > 0) . PHP_EOL; // 1 echo $number->isGreaterThan(10) . PHP_EOL; // 1 $number = new BigNumber(20); echo ($number->compareTo(20) === 0) . PHP_EOL; // 1 echo $number->isLessThanOrEqualTo(20) . PHP_EOL; // 1 

 //  .     . $number = new BigNumber("9,223 372`036'854,775.808000"); echo $number . PHP_EOL; // 9223372036854775.808 

 //       62-   $number = new BigNumber('9223372036854775807'); $number = $number->pow(2)->convertToBase(62); echo $number . PHP_EOL; // 1wlVYJaWMuw53lV7Cg98qn 


Links



UPD:
A small library update has been released - a wrapper for the most primitive arithmetic operations only on integers of any size that can work without additional extensions (the native implementation uses BCMath or GMP if present).
We now write about our new developments and updates of open components on our blog AzaGroup.ru .

Source: https://habr.com/ru/post/168935/


All Articles