self::MAX - $b ) self::overflow(); return $a + $b; } public static function roundRatio( int $a, int $b, int $denominator ): int { if ( $a < 0 || $b < 0 || $denominator < 1 || $denominator > 100000000000 ) self::overflow(); $whole = intdiv( $a, $denominator ); if ( $b && $whole > intdiv( self::MAX, $b ) ) self::overflow(); $base = $whole * $b; $source = $a % $denominator; $q = 0; $r = 0; foreach ( str_split( decbin( $b ) ) as $bit ) { $q *= 2; $r *= 2; if ( $r >= $denominator ) { $r -= $denominator; ++$q; } if ( '1' === $bit ) { $r += $source; if ( $r >= $denominator ) { $r -= $denominator; ++$q; } } if ( $q > self::MAX ) self::overflow(); } return self::add( self::add( $base, $q ), 2 * $r >= $denominator ? 1 : 0 ); } public static function percentageUnits( mixed $value ): int { if ( is_float( $value ) && is_finite( $value ) ) $value = json_encode( $value, JSON_PRESERVE_ZERO_FRACTION ); if ( ! ( is_int( $value ) || is_string( $value ) ) || ! preg_match( '/^(?:0|[1-9][0-9]{0,2})(?:\.[0-9]{1,4})?$/D', (string) $value ) ) \ABeCe\Booking\Service\CatalogValidator::invalid( 'percentage', __( 'Enter a percentage from 0 to 100 with at most four decimal places.', 'abece-booking' ) ); $parts = explode( '.', (string) $value ); $units = (int) $parts[0] * 10000 + (int) str_pad( $parts[1] ?? '', 4, '0' ); if ( $units > 1000000 ) \ABeCe\Booking\Service\CatalogValidator::invalid( 'percentage', __( 'Enter a percentage from 0 to 100 with at most four decimal places.', 'abece-booking' ) ); return $units; } public static function percentage( int $units ): string { return intdiv( $units, 10000 ) . '.' . str_pad( (string) ( $units % 10000 ), 4, '0', STR_PAD_LEFT ); } public static function digits( string $currency ): int { return in_array( $currency, [ 'BIF','CLP','DJF','GNF','ISK','JPY','KMF','KRW','PYG','RWF','UGX','UYI','VND','VUV','XAF','XOF','XPF' ], true ) ? 0 : ( in_array( $currency, [ 'BHD','IQD','JOD','KWD','LYD','OMR','TND' ], true ) ? 3 : ( in_array( $currency, [ 'CLF','UYW' ], true ) ? 4 : 2 ) ); } public static function format( int $minor, string $currency ): string { $digits = self::digits( $currency ); $factor = 10 ** $digits; return intdiv( $minor, $factor ) . ( $digits ? '.' . str_pad( (string) ( $minor % $factor ), $digits, '0', STR_PAD_LEFT ) : '' ) . ' ' . $currency; } private static function overflow(): never { throw new CatalogException( 'pricing_configuration_error', __( 'The configured price exceeds the supported money range.', 'abece-booking' ), 400 ); } }