amount_minor = $amount_minor; $this->currency = $currency; } /** * Zero amount in the given currency. * * @param string $currency ISO 4217 code. * * @since 0.1.0 */ public static function zero( string $currency ): self { return new self( 0, $currency ); } /** * Amount in minor units. * * @since 0.1.0 */ public function amount_minor(): int { return $this->amount_minor; } /** * ISO currency code. * * @since 0.1.0 */ public function currency(): string { return $this->currency; } /** * Whether the amount is zero. * * @since 0.1.0 */ public function is_zero(): bool { return 0 === $this->amount_minor; } /** * Whether the amount is negative. * * @since 0.1.0 */ public function is_negative(): bool { return $this->amount_minor < 0; } /** * Add another Money of the same currency. * * @param Money $other Other amount. * * @throws DomainException On currency mismatch. * * @since 0.1.0 */ public function add( self $other ): self { $this->assert_same_currency( $other ); return new self( $this->amount_minor + $other->amount_minor, $this->currency ); } /** * Subtract another Money of the same currency. * * @param Money $other Other amount. * * @throws DomainException On currency mismatch. * * @since 0.1.0 */ public function subtract( self $other ): self { $this->assert_same_currency( $other ); return new self( $this->amount_minor - $other->amount_minor, $this->currency ); } /** * Multiply by an integer factor. * * @param int $factor Non-negative multiplier (e.g. quantity). * * @throws DomainException When factor is negative. * * @since 0.1.0 */ public function multiply( int $factor ): self { if ( $factor < 0 ) { throw new DomainException( 'money.negative_factor', 'Money multiply factor must not be negative.' ); } return new self( $this->amount_minor * $factor, $this->currency ); } /** * Take a whole-number percentage of this amount (0–100), half-away-from-zero. * * @param int $percent Percentage 0–100. * * @throws DomainException When percent is out of range. * * @since 0.1.0 */ public function percent( int $percent ): self { if ( $percent < 0 || $percent > 100 ) { throw new DomainException( 'money.percent_range', 'Percent must be between 0 and 100 inclusive.' ); } return new self( self::divide_round( $this->amount_minor * $percent, 100 ), $this->currency ); } /** * Allocate a rational fraction of this amount (numerator/denominator), half-away-from-zero. * * @param int $numerator Fraction numerator. * @param int $denominator Fraction denominator (non-zero). * * @throws DomainException When denominator is zero. * * @since 0.1.0 */ public function allocate( int $numerator, int $denominator ): self { if ( 0 === $denominator ) { throw new DomainException( 'money.divide_by_zero', 'Cannot allocate with a zero denominator.' ); } return new self( self::divide_round( $this->amount_minor * $numerator, $denominator ), $this->currency ); } /** * Integer division with half-away-from-zero rounding (docs/11). * * @param int $numerator Numerator. * @param int $denominator Denominator (non-zero). * * @throws DomainException When denominator is zero. * * @since 0.1.0 */ public static function divide_round( int $numerator, int $denominator ): int { if ( 0 === $denominator ) { throw new DomainException( 'money.divide_by_zero', 'Cannot divide by zero.' ); } $negative = ( $numerator < 0 ) !== ( $denominator < 0 ); $n = abs( $numerator ); $d = abs( $denominator ); $q = intdiv( $n, $d ); $r = $n % $d; // Halfway and above rounds away from zero. if ( ( 2 * $r ) >= $d ) { ++$q; } return $negative ? -$q : $q; } /** * Whether equal to another instance. * * @param Money $other Other amount. * * @since 0.1.0 */ public function equals( self $other ): bool { return $this->amount_minor === $other->amount_minor && $this->currency === $other->currency; } /** * Assert matching currencies. * * @param Money $other Other amount. * * @throws DomainException On currency mismatch. */ private function assert_same_currency( self $other ): void { if ( $this->currency !== $other->currency ) { throw new DomainException( 'money.currency_mismatch', 'Cannot operate on Money values with different currencies.' ); } } }