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

Задача: У нас есть большое число с кучей нулей, или может просто большое число, которое неудобно читать. Нам нужно преобразовать его в удобное для чтение число. Например, число 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.3
 */
class Num_Format implements Num_Format_Interface {

	/**
	 * Format number. Thousands become k: 23 000 > 23k.
	 *
	 * @see Num_FormatTest::test__human_k()
	 *
	 * @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}.
	 */
	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.
	 *
	 * @see Num_FormatTest::test__human_short()
	 *
	 * @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}.
	 */
	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.
	 *
	 * @see Num_FormatTest::test__human_abbr()
	 *
	 * @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}.
	 */
	public function human_abbr( $number, $decimals = 1 ): string {

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

		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.
	 */
	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.
	 */
	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.
	 *
	 * @see Num_FormatTest::test__smart()
	 *
	 * Example:
	 * - 0.0000000123 > 0.000000012;
	 * - 0.0123 > 0.012;
	 * - 2.999951132432 > 2.999951;
	 *
	 * @param float|string $number
	 * @param int|null     $show_decimals
	 */
	public function smart( $number, int $show_decimals = 2 ): string {

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

		$decimals = $show_decimals;
		$abs_number = abs( $number );

		if(     $abs_number < 0.1 ){  $decimals += 2; $show_decimals = 2; }
		elseif( $abs_number < 1   ){  $decimals += 1; $show_decimals = 2; }

		// use simple formats fo big numbers
		if( $abs_number < 30 && $show_decimals > 1 ){
			$number = (float) $number;
			// increase $decimals for numbers like: n.00nnn | n.99nnn
			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.
	 *
	 * @see Num_FormatTest::test__flex()
	 * @see number_format_i18n()
	 *
	 * @param float|string $number
	 * @param int          $decimals  Optional. Precision of number of decimal places. Default 0.
	 */
	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.
	 *
	 * @see Num_FormatTest::test__fixed()
	 * @see number_format_i18n()
	 *
	 * @param float|string $number
	 * @param int          $decimals  Optional. Precision of number of decimal places. Default 0.
	 */
	public function fixed( $number, int $decimals = 2 ): string {

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

		return number_format_i18n( $number, $decimals );
	}

}

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

GitHub
<?php

class Num_FormatTest extends extends \WP_Mock\Tools\TestCase {

	public function setUp(): void {
		parent::setUp();

		$GLOBALS['wp_locale'] = (object) [
			'number_format' => [
				'thousands_sep' => ' ',
				'decimal_point' => ',',
			],
		];

		\WP_Mock::userFunction( 'number_format_i18n' )->andReturnUsing( static function( $number, $decimals = 0 ) {
			global $wp_locale;

			return number_format( $number, absint( $decimals ), $wp_locale->number_format['decimal_point'], $wp_locale->number_format['thousands_sep'] );
		} );
	}

	public function tearDown(): void {
		unset( $GLOBALS['wp_locale'] );
		parent::tearDown();
	}

	/**
	 * @covers Num_Format::human_abbr
	 */
	public function test__human_abbr(): void {
		$this->assertSame( '0', ( new Num_Format() )->human_abbr( false ) );
		$this->assertSame( '0', ( new Num_Format() )->human_abbr( null ) );
		$this->assertSame( '0', ( new Num_Format() )->human_abbr( 0 ) );
		$this->assertSame( '1,1', ( new Num_Format() )->human_abbr( 1.0654 ) );
		$this->assertSame( '16', ( new Num_Format() )->human_abbr( 16 ) );
		$this->assertSame( '1,7 тыс.', ( new Num_Format() )->human_abbr( 1654 ) );
		$this->assertSame( '16,5 тыс.', ( new Num_Format() )->human_abbr( 16504.234 ) );
		$this->assertSame( '16,5 тыс.', ( new Num_Format() )->human_abbr( 16504.0000234 ) );
		$this->assertSame( '254,9 млн.', ( new Num_Format() )->human_abbr( 254854564 ) );
		$this->assertSame( '-254,9 млн.', ( new Num_Format() )->human_abbr( -254854564 ) );
	}

	/**
	 * @covers Num_Format::human_short
	 */
	public function test__human_short(): void {
		$this->assertSame( '16,5K', ( new Num_Format() )->human_short( 16504.0000234 ) );
		$this->assertSame( '254,9M', ( new Num_Format() )->human_short( 254854564 ) );
		$this->assertSame( '25,5B', ( new Num_Format() )->human_short( 25485456411 ) );
		$this->assertSame( '2,5T', ( new Num_Format() )->human_short( 2548545641111 ) );
		$this->assertSame( '2,5Q', ( new Num_Format() )->human_short( 2548545641111999 ) );
	}

	/**
	 * @covers Num_Format::human_k
	 */
	public function test__human_k(): void {
		$this->assertSame( '0', ( new Num_Format() )->human_k( null ) );
		$this->assertSame( '0', ( new Num_Format() )->human_k( false ) );
		$this->assertSame( '0', ( new Num_Format() )->human_k( 0 ) );
		$this->assertSame( '16,6kk', ( new Num_Format() )->human_k( 16565404.0000234 ) );
		$this->assertSame( '254,9kkk', ( new Num_Format() )->human_k( 254856544564 ) );

		$this->assertSame( '2,0000023', ( new Num_Format() )->human_k( 2.00000231, '2 smart' ) );
		$this->assertSame( '2,00', ( new Num_Format() )->human_k( 2.00000231, '2 fixed' ) );
		$this->assertSame( '2', ( new Num_Format() )->human_k( 2.00000231, '2 flex' ) );
		$this->assertSame( '2,232', ( new Num_Format() )->human_k( 2.2317, '3 smart' ) );
		$this->assertSame( '2,232', ( new Num_Format() )->human_k( 2.2317, '3 fixed' ) );
		$this->assertSame( '2,232', ( new Num_Format() )->human_k( 2.2317, '3 flex' ) );
		$this->assertSame( '-2,232', ( new Num_Format() )->human_k( -2.2317, '3 flex' ) );
	}

	/**
	 * @covers Num_Format::fixed
	 */
	public function test__fixed(): void {
		$this->assertSame( '0,000', ( new Num_Format() )->fixed( 0.000111, 3 ) );
		$this->assertSame( '1,011', ( new Num_Format() )->fixed( 1.0111, 3 ) );
		$this->assertSame( '254,00', ( new Num_Format() )->fixed( 254 ) );
	}

	/**
	 * @covers Num_Format::smart
	 */
	public function test__smart(): void {
		$this->assertSame( '0,00011', ( new Num_Format() )->smart( 0.0001111, 2 ) );
		$this->assertSame( '1,00011', ( new Num_Format() )->smart( 1.0001111, 2 ) );
		$this->assertSame( '254 854 564', ( new Num_Format() )->smart( 254854564 ) );

		// zero decimal
		$this->assertSame( '24', ( new Num_Format() )->smart( 23.54, 0 ) );
		$this->assertSame( '24', ( new Num_Format() )->smart( 23.5, 0 ) );
		$this->assertSame( '1', ( new Num_Format() )->smart( 1.2, 0 ) );
		$this->assertSame( '0,9', ( new Num_Format() )->smart( 0.85, 0 ) );
		$this->assertSame( '0,8', ( new Num_Format() )->smart( 0.83, 0 ) );
		$this->assertSame( '0,07', ( new Num_Format() )->smart( 0.073, 0 ) );
		$this->assertSame( '0,0063', ( new Num_Format() )->smart( 0.0063, 0 ) );
		$this->assertSame( '0,003', ( new Num_Format() )->smart( 0.00301, 0 ) );
		$this->assertSame( '0,00053', ( new Num_Format() )->smart( 0.00053, 0 ) );
		$this->assertSame( '0,000063', ( new Num_Format() )->smart( 0.000063, 0 ) );
		$this->assertSame( '0,0000073', ( new Num_Format() )->smart( 0.0000073, 0 ) );
		$this->assertSame( '0,00000083', ( new Num_Format() )->smart( 0.00000083, 0 ) );
		$this->assertSame( '0,000000093', ( new Num_Format() )->smart( 0.000000093, 0 ) );
		$this->assertSame( '0,0000000013', ( new Num_Format() )->smart( 0.0000000013, 0 ) );
	}

	/**
	 * @covers Num_Format::flex
	 */
	public function test__flex(): void {
		$this->assertSame( '16 504', ( new Num_Format() )->flex( 16504.0000234 ) );
		$this->assertSame( '16 504,01', ( new Num_Format() )->flex( 16504.0100 ) );
		$this->assertSame( '254 854 564', ( new Num_Format() )->flex( 254854564 ) );
	}
}

--

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