WordPress как на ладони

Сокращение (округление) больших чисел до читаемых тыс. млн. млрд.

Задача: У нас есть большое число с кучей нулей, или может просто большое число, которое неудобно читать. Нам нужно преобразовать его в удобное для чтение число. Например, число 1500, должно превратиться в 1,5 тыс..

В WP есть подобная функция, только для преобразования байтов в килобайты, мегабайты. См. size_format().

Решение: конвертация больших чисел в текстовый вид

Код ниже преобразовывает переданное число в читаемую форму 1 тысяча, 1 миллион, 1 миллиард.

Вариант 1 (рекурсия)

/**
 * Convert big number to readable format.
 *
 * @param int|string $num          Number to format.
 * @param int        $decimals     Optional. Precision of number of decimal places. Default: 0.
 * @param string     $return_type  Optional. Return format. One of: `string`, `object`. Default: string.
 * @param int        $_depth       Internal. For recursion.
 *
 * @return string|object Object with two elements: `num` and `unit`.
 *
 * @version 1.3
 */
function number_to_human( $number, int $decimals = 1, string $return_type = 'string', int $_depth = 0 ) {

	static $abbrs;
	$abbrs || $abbrs = [ '',
		__( 'тыс.', 'hl' ),
		__( 'млн.', 'hl' ),
		__( 'млрд.', 'hl' ),
		__( 'трлн.', 'hl' ),
		__( 'квдрлн.', 'hl' )
	];

	if( $number >= 1000 ){
		return call_user_func( __FUNCTION__, $number / 1000, $decimals, $return_type, ++$_depth );
	}

	$number = number_format_i18n( $number, $decimals );

	$float_sign = $GLOBALS['wp_locale']->number_format['decimal_point'];
	if( strpos( $number, $float_sign ) ){
		$number = rtrim( $number, '0' );
		$number = rtrim( $number, $float_sign );
	}

	$abbr = $_depth ? $abbrs[ $_depth ] : '';

	if( 'string' === $return_type ){
		return trim( "$number $abbr" );
	}

	return (object) [
		'num'  => $number,
		'abbr' => $abbr,
	];
}
$arr = [
	number_to_human( 0 ),            // 0
	number_to_human( 120 ),          // 120
	number_to_human( 16 ),           // 16
	number_to_human( 1654, 2 ),      // 1,65 тыс.
	number_to_human( 16504.234 ),    // 16,5 тыс.
	number_to_human( 16504.234, 2 ), // 16,5 тыс.
	number_to_human( 254854564, 2 ), // 254,85 млн.
];

/*
Array (
	[0] => 0
	[1] => 120
	[2] => 16
	[3] => 1,65 тыс.
	[4] => 16,5 тыс.
	[5] => 16,5 тыс.
	[6] => 254,85 млн.
)
*/
<?php $data = number_to_human( 254854564, 2, 'object' ) ?>

<div class="price">
	<span class="number"><?= $data->num; // 254,9 ?></span>
	<span class="abbr"><?= $data->abbr; // млн. ?></span>
</div>

Вариант 2 (без рекурсии)

/**
 * Convert big number to readable format.
 *
 * @param int|float|string $num       Number to format.
 * @param int              $decimals  Optional. Precision of number of decimal places. Default: 0.
 *
 * @return object Object with two elements: `num` and `unit`.
 *
 * @version 1.1
 */
function num_to_human( $num, int $decimals = 1 ) {

	static $units;
	$units || $units = [
		'квдрлн.' => 1000 ** 5,
		'трлн.'   => 1000 ** 4,
		'млрд.'   => 1000 ** 3,
		'млн.'    => 1000 ** 2,
		'тыс.'    => 1000,
		''        => 1,
	];

	$number = 0;

	foreach( $units as $unit => $mag ){
		if ( (float) $num >= $mag ) {
			$number = $num / $mag;
			break;
		}
	}

	$number = number_format_i18n( $number, $decimals );

	if( preg_match( '/[,.]/', $number, $match ) ){
		$number = rtrim( $number, '0' );
		$number = rtrim( $number, $match[0] );
	}

	return (object) [
		'num' => $number,
		'unit' => $unit
	];
}

Пример использования:

$arr = [
	num_to_human( 0 ),            // 0
	num_to_human( 120 ),          // 120
	num_to_human( 16 ),           // 16
	num_to_human( 1654, 2 ),      // 1,65 тыс.
	num_to_human( 16504.234 ),    // 16,5 тыс.
	num_to_human( 16504.234, 2 ), // 16,5 тыс.
	num_to_human( 254854564, 2 ), // 254,85 млн.
];

foreach( $arr as $res ){
	echo "$res->num $res->unit\n";
}

/*
0
120
16
1,65 тыс.
16,5 тыс.
16,5 тыс.
254,85 млн.
*/

Класс Объединяющий в себе несколько функций

GitHub
<?php

namespace Kama\WP;

interface Num_Format_Interface {

	public function human_k( $number, $decimals = 1 ): string;
	public function human_short( $number, $decimals = 1 ): string;
	public function human_abbr( $number, $decimals = 1 ): string;

	public function smart( $number, int $show_decimals = 2 ): string;
	public function flex( $number, int $decimals = 2 ): string;
	public function fixed( $number, int $decimals = 2 ): string;
}

/**
 *
 * @see number_format_i18n()
 *
 * @version 3.2
 */
class Num_Format implements Num_Format_Interface {

	/**
	 * Format number. Thousands become k: 23 000 > 23k.
	 *
	 * @param float|string $number
	 * @param int|string   $decimals  Optional. Precision of number of decimal places. Default 0.
	 *                                Specify string as 'decimal float_round_type': '3 flex', '2 smart', '3 fixed'
	 *                                to set float round function. {@see _human_unit}.
	 *
	 * @return string
	 */
	public function human_k( $number, $decimals = 1 ): string {

		static $names;
		$names || $names = [ '', 'k', 'kk', 'kkk', 'kkkk', 'kkkkk' ];

		return $this->_human_unit( $number, $decimals, $names, '%s' );
	}

	/**
	 * Format number. Example: 23 000 000 > 23M.
	 *
	 * @param float|string $number
	 * @param int|string   $decimals  Optional. Precision of number of decimal places. Default 0.
	 *                                Specify string as 'decimal float_round_type': '3 flex', '2 smart', '3 fixed'
	 *                                to set float round function. {@see _human_unit}.
	 *
	 * @return string
	 */
	public function human_short( $number, $decimals = 1 ): string {

		static $names;
		$names || $names = [ '', 'K', 'M', 'B', 'T', 'Q' ];

		return $this->_human_unit( $number, $decimals, $names, '%s' );
	}

	/**
	 * Convert big number to readable format.
	 *
	 * @param int|string $num       Original Number.
	 * @param int|string $decimals  Optional. Precision of number of decimal places. Default 0.
	 *                              Specify string as 'decimal float_round_type': '3 flex', '2 smart', '3 fixed'
	 *                              to set float round function. {@see _human_unit}.
	 *
	 * @return string
	 */
	public function human_abbr( $number, $decimals = 1 ): string {

		static $names;
		$names || $names = [ '',
			__( 'тыс.', 'hl' ),
			__( 'млн.', 'hl' ),
			__( 'млрд.', 'hl' ),
			__( 'трлн.', 'hl' ),
			__( 'квдрлн.', 'hl' ),
		];

		return $this->_human_unit( $number, $decimals, $names, ' %s' );
	}

	/**
	 *
	 * @param float|string $number    Original Number.
	 * @param int|string   $decimals  Optional. Precision of number of decimal places. Default 0.
	 *                                Specify string as 'decimal float_round_type': '3 flex', '2 smart', '3 fixed'
	 *                                to set float round function.
	 * @param array        $names
	 * @param string       $unit_patt Pattern to display units.
	 *
	 * @return string
	 */
	private function _human_unit( $number, $decimals, array $names, string $unit_patt = '' ): string {

		[ $number, $depth ] = self::_human_unit_depth( $number );

		if( ! $number ){
			return '0';
		}

		[ $decimals, $floats_round_type ] = explode( ' ', $decimals ) + [ 2, '' ];

		$unit_suffix = $depth ? sprintf( $unit_patt, $names[ $depth ] ) : '';

		switch( $floats_round_type ){

			case 'fixed':
				return $this->fixed( $number, $decimals ) . $unit_suffix;

			case 'smart':
				return $this->smart( $number, $decimals ) . $unit_suffix;

			default:
				return $this->flex( $number, $decimals ) . $unit_suffix;
		}

	}

	/**
	 * Convert big number to readable format.
	 * Supports negative numbers.
	 * Can be used as root function. I.e. wrap it to your
	 * own function where pass $names and specify desired output.
	 *
	 * @param float|string $number  Original Number.
	 * @param int          $depth   Internal.
	 *
	 * @return array
	 */
	private static function _human_unit_depth( $number, int $depth = 0 ): array {

		$sign = $number <=> 0;
		$number = abs( $number );

		if( $number >= 1000 ){
			return self::_human_unit_depth( $number * $sign / 1000, ++$depth );
		}

		return [ $number * $sign, $depth ];
	}

	/**
	 * Format number. Smart calculate zeros after dot and
	 * leave specified $show_decimals numbers of digits.
	 *
	 * Example:
	 * - 0.0000000123 > 0.000000012;
	 * - 0.0123 > 0.012;
	 * - 2.999951132432 > 2.999951;
	 *
	 * @param float|string $number
	 * @param int|null     $show_decimals
	 *
	 * @return string
	 */
	public function smart( $number, int $show_decimals = 2 ): string {

		if( ! $number ){
			return '';
		}

		$decimals = $show_decimals;

		// use simple formats fo big numbers
		if( $number < 30 && $number > -30 ){

			$number = (float) $number;
			// increase $decimals for numbers like: n.000nnn | n.999nnn
			preg_match( "/\d+\.((?:0{2,}|9{2,})\d{1,$show_decimals})/", sprintf( '%.12f', $number ), $mm );
			if( $mm ){
				$decimals = strlen( $mm[1] );
			}
		}

		return $this->flex( $number, $decimals );
	}

	/**
	 * Format number and trim ending zeros.
	 *
	 * @param float|string $number
	 * @param int          $decimals  Optional. Precision of number of decimal places. Default 0.
	 *
	 * @return string
	 * @see number_format_i18n()
	 */
	public function flex( $number, int $decimals = 2 ): string {

		if( ! $number ){
			return '';
		}

		$number = $this->fixed( $number, $decimals );

		// 38 020.00 > 38 020
		// 38 020.00100 > 38 020.001
		// 38 020 > 38 020
		$float_sign = $GLOBALS['wp_locale']->number_format['decimal_point'];
		if( strpos( $number, $float_sign ) ){
			$number = rtrim( $number, '0' );
			$number = rtrim( $number, $float_sign );
		}

		return $number;
	}

	/**
	 * Format number and always leave specified ending decimals.
	 *
	 * @param float|string $number
	 * @param int          $decimals  Optional. Precision of number of decimal places. Default 0.
	 *
	 * @return string
	 * @see number_format_i18n()
	 */
	public function fixed( $number, int $decimals = 2 ): string {

		if( ! $number ){
			return '';
		}

		return number_format_i18n( $number, $decimals );
	}

}


Пример использования:

GitHub
<?php

$format = new \Kama\WP\Num_Format();

$test = [
	$format->human_abbr( 0 ),              // 0
	$format->human_abbr( 1.0654 ),         // 1,1
	$format->human_abbr( 16 ),             // 16
	$format->human_abbr( 1654 ),           // 1,7 тыс.
	$format->human_abbr( 16504.234 ),      // 16,5 тыс.
	$format->human_abbr( 16504.0000234 ),  // 16,5 тыс.
	$format->human_abbr( 254854564 ),      // 254,9 млн.
	'',
	$format->human_k( 16565404.0000234 ),  // 16,6kk
	$format->human_k( 254856544564 ),      // 254,9kkk
	'',
	$format->human_k( 2.00000231, '2 smart' ), // 2,0000023
	$format->human_k( 2.00000231, '2 fixed' ), // 2,00
	$format->human_k( 2.00000231, '2 flex' ),  // 2
	$format->human_k( 2.231, '3 smart' ),      // 2,23
	$format->human_k( 2.231, '3 fixed' ),      // 2,23
	$format->human_k( 2.231, '3 flex' ),       // 2,23
	'',
	$format->human_short( 16504.0000234 ), // 16,5K
	$format->human_short( 254854564 ),     // 254,9M
	'',
	$format->flex( 16504.0000234 ),      // 16 504
	$format->flex( 16504.0100 ),         // 16 504,01
	$format->flex( 254854564 ),          // 254 854 564
	'',
	$format->smart( 0.000111, 2 ),  // 0,00011
	$format->smart( 1.000111, 2 ),  // 1,00011
	$format->smart( 254854564 ),    // 254 854 564
	'',
	$format->fixed( 0.000111, 3 ), // 0,000
	$format->fixed( 1.0111, 3 ),   // 1,011
	$format->fixed( 254 ),         // 254,00
];

print_r( $test );

--

П.С. Не редко слышу как числа называют цифрами. Например, "У него на счете большая цифра", "Цифра с шестью нулями". Не делайте так!

2 комментария
    Войти