1<?php
2
3/**
4 * This file is part of the ramsey/uuid library
5 *
6 * For the full copyright and license information, please view the LICENSE
7 * file that was distributed with this source code.
8 *
9 * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
10 * @license http://opensource.org/licenses/MIT MIT
11 */
12
13declare(strict_types=1);
14
15namespace Ramsey\Uuid\Math;
16
17use Brick\Math\BigDecimal;
18use Brick\Math\BigInteger;
19use Brick\Math\Exception\MathException;
20use Brick\Math\RoundingMode as BrickMathRounding;
21use Ramsey\Uuid\Exception\InvalidArgumentException;
22use Ramsey\Uuid\Type\Decimal;
23use Ramsey\Uuid\Type\Hexadecimal;
24use Ramsey\Uuid\Type\Integer as IntegerObject;
25use Ramsey\Uuid\Type\NumberInterface;
26
27/**
28 * A calculator using the brick/math library for arbitrary-precision arithmetic
29 *
30 * @psalm-immutable
31 */
32final class BrickMathCalculator implements CalculatorInterface
33{
34    private const ROUNDING_MODE_MAP = [
35        RoundingMode::UNNECESSARY => BrickMathRounding::UNNECESSARY,
36        RoundingMode::UP => BrickMathRounding::UP,
37        RoundingMode::DOWN => BrickMathRounding::DOWN,
38        RoundingMode::CEILING => BrickMathRounding::CEILING,
39        RoundingMode::FLOOR => BrickMathRounding::FLOOR,
40        RoundingMode::HALF_UP => BrickMathRounding::HALF_UP,
41        RoundingMode::HALF_DOWN => BrickMathRounding::HALF_DOWN,
42        RoundingMode::HALF_CEILING => BrickMathRounding::HALF_CEILING,
43        RoundingMode::HALF_FLOOR => BrickMathRounding::HALF_FLOOR,
44        RoundingMode::HALF_EVEN => BrickMathRounding::HALF_EVEN,
45    ];
46
47    public function add(NumberInterface $augend, NumberInterface ...$addends): NumberInterface
48    {
49        $sum = BigInteger::of($augend->toString());
50
51        foreach ($addends as $addend) {
52            $sum = $sum->plus($addend->toString());
53        }
54
55        return new IntegerObject((string) $sum);
56    }
57
58    public function subtract(NumberInterface $minuend, NumberInterface ...$subtrahends): NumberInterface
59    {
60        $difference = BigInteger::of($minuend->toString());
61
62        foreach ($subtrahends as $subtrahend) {
63            $difference = $difference->minus($subtrahend->toString());
64        }
65
66        return new IntegerObject((string) $difference);
67    }
68
69    public function multiply(NumberInterface $multiplicand, NumberInterface ...$multipliers): NumberInterface
70    {
71        $product = BigInteger::of($multiplicand->toString());
72
73        foreach ($multipliers as $multiplier) {
74            $product = $product->multipliedBy($multiplier->toString());
75        }
76
77        return new IntegerObject((string) $product);
78    }
79
80    public function divide(
81        int $roundingMode,
82        int $scale,
83        NumberInterface $dividend,
84        NumberInterface ...$divisors
85    ): NumberInterface {
86        $brickRounding = $this->getBrickRoundingMode($roundingMode);
87
88        $quotient = BigDecimal::of($dividend->toString());
89
90        foreach ($divisors as $divisor) {
91            $quotient = $quotient->dividedBy($divisor->toString(), $scale, $brickRounding);
92        }
93
94        if ($scale === 0) {
95            return new IntegerObject((string) $quotient->toBigInteger());
96        }
97
98        return new Decimal((string) $quotient);
99    }
100
101    public function fromBase(string $value, int $base): IntegerObject
102    {
103        try {
104            return new IntegerObject((string) BigInteger::fromBase($value, $base));
105        } catch (MathException | \InvalidArgumentException $exception) {
106            throw new InvalidArgumentException(
107                $exception->getMessage(),
108                (int) $exception->getCode(),
109                $exception
110            );
111        }
112    }
113
114    public function toBase(IntegerObject $value, int $base): string
115    {
116        try {
117            return BigInteger::of($value->toString())->toBase($base);
118        } catch (MathException | \InvalidArgumentException $exception) {
119            throw new InvalidArgumentException(
120                $exception->getMessage(),
121                (int) $exception->getCode(),
122                $exception
123            );
124        }
125    }
126
127    public function toHexadecimal(IntegerObject $value): Hexadecimal
128    {
129        return new Hexadecimal($this->toBase($value, 16));
130    }
131
132    public function toInteger(Hexadecimal $value): IntegerObject
133    {
134        return $this->fromBase($value->toString(), 16);
135    }
136
137    /**
138     * Maps ramsey/uuid rounding modes to those used by brick/math
139     */
140    private function getBrickRoundingMode(int $roundingMode): int
141    {
142        return self::ROUNDING_MODE_MAP[$roundingMode] ?? 0;
143    }
144}
145