j84reginato/my-eval

View on GitHub
src/Parsing/Nodes/Operand/RationalNode.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php

declare(strict_types=1);

namespace MyEval\Parsing\Nodes\Operand;

use MyEval\Exceptions\DivisionByZeroException;
use MyEval\Parsing\Nodes\Node;
use MyEval\Solving\Visitor;

use function get_class;

/**
 * AST node representing a rational operand number.
 */
class RationalNode extends NumericNode
{
    /**
     * @param float $value The value of the represented number.
     */
    public readonly float $value;

    /**
     * @param int  $p The numerator of the represented number.
     * @param int  $q The denominator of the represented number.
     * @param bool $normalize
     *
     * @throws DivisionByZeroException
     */
    public function __construct(public int $p, public int $q, bool $normalize = true)
    {
        if ($q === 0) {
            throw new DivisionByZeroException();
        }

        if ($normalize) {
            $this->normalize();
        }

        $this->value = (float)((1.0 * $this->p) / $this->q);
    }

    /**
     * Single function in the Visitable interface.
     *
     * Calling visitRationalNode() function on a Visitor class.
     *
     * ## Example:
     * - evaluators: StdMathEvaluator, RationalEvaluator, ComplexEvaluator, Differentiator, LogicEvaluator or
     * - printers: ASCIIPrinter, LaTeXPrinter, TreePrinter.
     *
     * @param Visitor $visitor
     *
     * @return mixed
     */
    public function accept(Visitor $visitor): mixed
    {
        return $visitor->visitRationalNode($this);
    }

    /**
     * Helper function, comparing two ASTs.
     *
     * Useful for testing and also for some AST transformers.
     *
     * @param OperandNode $other Compare to this tree.
     *
     * @return bool
     */
    public function compareTo(Node $other): bool
    {
        return match (get_class($other)) {
            IntegerNode::class => $this->getDenominator() === 1 && $this->getNumerator() === $other->value,
            static::class      =>
                $this->getNumerator() === $other->getNumerator() &&
                $this->getDenominator() === $other->getDenominator(),
            default            => parent::compareTo($other),
        };
    }

    /**
     * Apply normalization to the rational number.
     *
     * @return void
     */
    private function normalize(): void
    {
        $a = $this->p;
        $b = $this->q;

        while ($b !== 0) {
            $m = $a % $b;
            $a = $b;
            $b = $m;
        }

        $gcd     = $a;
        $this->p /= $gcd;
        $this->q /= $gcd;

        if ($this->q < 0) {
            $this->q = -$this->q;
            $this->p = -$this->p;
        }
    }

    /**
     * Return the numerator of the rational number.
     *
     * @return int
     */
    public function getNumerator(): int
    {
        return $this->p;
    }

    /**
     * Return the denominator of the rational number.
     *
     * @return int
     */
    public function getDenominator(): int
    {
        return $this->q;
    }
}