Skip to content

Commit edc3b7f

Browse files
committed
Added fixed exponent and automatic precision flag.
1 parent 70e5d95 commit edc3b7f

File tree

4 files changed

+218
-9
lines changed

4 files changed

+218
-9
lines changed

README.md

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ needed.
5656
```
5757
> 512.5 KiB
5858
59+
Automatic precision scaling can be disabled if this behaviour is undesired.
60+
61+
```php
62+
(new ByteFormatter)->setPrecision(2)->disableAutomaticPrecision()->format(0x80200);
63+
```
64+
> 512.50 KiB
65+
5966
The default precision can be overridden by passing the second argument to `format()`.
6067

6168
```php
@@ -66,15 +73,41 @@ The default precision can be overridden by passing the second argument to `forma
6673
Output format
6774
-------------
6875

69-
The format can be changed by calling the `setFormat($format)` function which takes a string format parameter.
70-
The default format is `'%v %u'`. Occurrences of `%v` and `%u` in the format string will be replaced with the calculated
71-
*value* and *units* respectively.
76+
The format can be changed by calling `setFormat()` which takes a string format parameter. The default format is
77+
`'%v %u'`. Occurrences of `%v` and `%u` in the format string will be replaced with the calculated *value* and *units*
78+
respectively.
7279

7380
```php
7481
(new ByteFormatter)->setFormat('%v%u')->format(0x80000);
7582
```
7683
> 512KiB
7784
85+
Fixed exponent
86+
--------------
87+
88+
One of the main benefits of the formatter is an appropriate exponent is calculated automatically, however it is also
89+
possible to fix the exponent to a specific value using `setFixedExponent()`.
90+
91+
```php
92+
(new ByteFormatter)->setFixedExponent(1)->format(1024 * 1024);
93+
```
94+
> 1024 KiB
95+
96+
Normally we would expect the above example to output `1 MiB` but because the exponent is locked to `1` the output will
97+
always be in `KiB`. Consult the following table to see how exponents map to symbols.
98+
99+
| Exponent | Symbol |
100+
|:--------:|:------:|
101+
| 0 | B |
102+
| 1 | K |
103+
| 2 | M |
104+
| 3 | G |
105+
| 4 | T |
106+
| 5 | P |
107+
| 6 | E |
108+
| 7 | Z |
109+
| 8 | Y |
110+
78111
Unit customization
79112
------------------
80113

src/Byte/ByteFormatter.php

Lines changed: 127 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ class ByteFormatter
2323
/** @var int */
2424
private $precision = 0;
2525

26+
/** @var bool */
27+
private $automaticPrecision = true;
28+
29+
/** @var int */
30+
private $exponent;
31+
2632
/** @var UnitDecorator */
2733
private $unitDecorator;
2834

@@ -51,14 +57,48 @@ public function __construct(UnitDecorator $unitDecorator = null)
5157
public function format($bytes, $precision = null)
5258
{
5359
// Use default precision when not specified.
54-
$precision === null && $precision = $this->precision;
60+
$precision === null && $precision = $this->getPrecision();
61+
62+
$log = log($bytes, $this->getBase());
63+
$exponent = $this->hasFixedExponent() ? $this->getFixedExponent() : max(0, $log|0);
64+
$value = round(pow($this->getBase(), $log - $exponent), $precision);
65+
$units = $this->getUnitDecorator()->decorate($exponent, $this->getBase(), $value);
66+
67+
return trim(sprintf($this->sprintfFormat, $this->formatValue($value, $precision), $units));
68+
}
69+
70+
/**
71+
* Formats the specified number with the specified precision.
72+
*
73+
* If precision scaling is enabled the precision may be reduced when it
74+
* contains non-significant digits. If the fractional part is zero it may
75+
* be removed entirely.
76+
*
77+
* @param float $value Number.
78+
* @param $precision Number of fractional digits.
79+
*
80+
* @return string Formatted number.
81+
*/
82+
private function formatValue($value, $precision)
83+
{
84+
$formatted = sprintf("%0.${precision}F", $value);
85+
86+
if ($this->hasAutomaticPrecision()) {
87+
// [0 => integer part, 1 => fractional part].
88+
$formattedParts = explode('.', $formatted);
5589

56-
$log = log($bytes, $this->base);
57-
$exponent = max(0, $log|0);
58-
$value = round(pow($this->base, $log - $exponent), $precision);
59-
$units = $this->getUnitDecorator()->decorate($exponent, $this->base, $value);
90+
if (isset($formattedParts[1])) {
91+
// Strip trailing 0s in fractional part.
92+
if (!$formattedParts[1] = chop($formattedParts[1], '0')) {
93+
// Remove fractional part.
94+
unset($formattedParts[1]);
95+
}
6096

61-
return trim(sprintf($this->sprintfFormat, $value, $units));
97+
$formatted = join('.', $formattedParts);
98+
}
99+
}
100+
101+
return $formatted;
62102
}
63103

64104
/**
@@ -146,6 +186,87 @@ public function setPrecision($precision)
146186
return $this;
147187
}
148188

189+
/**
190+
* Enables automatic precision scaling.
191+
*
192+
* @return $this
193+
*/
194+
public function enableAutomaticPrecision()
195+
{
196+
$this->automaticPrecision = true;
197+
198+
return $this;
199+
}
200+
201+
/**
202+
* Disables automatic precision scaling.
203+
*
204+
* @return $this
205+
*/
206+
public function disableAutomaticPrecision()
207+
{
208+
$this->automaticPrecision = false;
209+
210+
return $this;
211+
}
212+
213+
/**
214+
* Gets a value indicating whether precision will be scaled automatically.
215+
*
216+
* @return bool True if precision will be scaled automatically, otherwise
217+
* false.
218+
*/
219+
public function hasAutomaticPrecision()
220+
{
221+
return $this->automaticPrecision;
222+
}
223+
224+
/**
225+
* Gets the fixed exponent.
226+
*
227+
* @return int Fixed exponent.
228+
*/
229+
public function getFixedExponent()
230+
{
231+
return $this->exponent;
232+
}
233+
234+
/**
235+
* Sets the fixed exponent.
236+
*
237+
* @param int $exponent Fixed exponent.
238+
*
239+
* @return $this
240+
*/
241+
public function setFixedExponent($exponent)
242+
{
243+
$this->exponent = $exponent|0;
244+
245+
return $this;
246+
}
247+
248+
/**
249+
* Clears any fixed exponent.
250+
*
251+
* @return $this
252+
*/
253+
public function clearFixedExponent()
254+
{
255+
$this->exponent = null;
256+
257+
return $this;
258+
}
259+
260+
/**
261+
* Gets a value indicating whether a fixed exponent has been set.
262+
*
263+
* @return bool True if a fixed exponent has been set, otherwise false.
264+
*/
265+
public function hasFixedExponent()
266+
{
267+
return $this->exponent !== null;
268+
}
269+
149270
/**
150271
* Gets the unit decorator.
151272
*

test/Integration/Byte/ByteFormatterTest.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ public function provideDecimalIntegers()
7171
[1000000000000, '1T'],
7272
[1000000000000000, '1P'],
7373
[1000000000000000000, '1E'],
74+
[1000000000000000000000, '1Z'],
75+
[1000000000000000000000000, '1Y'],
7476
];
7577
}
7678

@@ -119,6 +121,49 @@ public function provideFormats()
119121
];
120122
}
121123

124+
/** @dataProvider provideFixedExponents */
125+
public function testFixedExponent($exponent, $bytes, $formatted)
126+
{
127+
$this->formatter->setPrecision(8);
128+
129+
$this->formatter->setFixedExponent($exponent);
130+
$this->assertSame($formatted, $this->formatter->format($bytes));
131+
}
132+
133+
public function provideFixedExponents()
134+
{
135+
return [
136+
// TODO: Investigate rounding errors in following two cases.
137+
[0, 0x8000000000, '549755813888.00134277'],
138+
[1, 0x8000000000, '536870912.00000131K'],
139+
[2, 0x8000000000, '524288M'],
140+
[3, 0x8000000000, '512G'],
141+
[4, 0x8000000000, '0.5T'],
142+
[5, 0x8000000000, '0.00048828P'],
143+
[6, 0x8000000000, '0.00000048E'],
144+
145+
[1, 0, '0K'],
146+
[1, pow(Base::BINARY, 0), '0.00097656K'],
147+
[1, pow(Base::BINARY, 1), '1K'],
148+
[1, pow(Base::BINARY, 2), '1024K'],
149+
[1, pow(Base::BINARY, 3), '1048576K'],
150+
[1, pow(Base::BINARY, 4), '1073741824K'],
151+
[1, pow(Base::BINARY, 5), '1099511627776K'],
152+
[1, pow(Base::BINARY, 6), '1125899906842624K'],
153+
[1, pow(Base::BINARY, 7), '1152921504606846976K'],
154+
[1, pow(Base::BINARY, 8), '1180591620717411303424K'],
155+
[1, pow(Base::BINARY, 9), '1208925819614629174706176K'],
156+
[1, pow(Base::BINARY, 10), '1237940039285380274899124224K'],
157+
];
158+
}
159+
160+
public function testDisableAutomaticPrecision()
161+
{
162+
$this->formatter->disableAutomaticPrecision();
163+
164+
$this->assertSame('512.50K', $this->formatter->format(0x80200, 2));
165+
}
166+
122167
public function testCustomUnitSequence()
123168
{
124169
$formatter = (new ByteFormatter)->setUnitDecorator(

test/Integration/DocumentationTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ public function testPrecision()
2323

2424
$this->assertSame('512.5 KiB', (new ByteFormatter)->setPrecision(2)->format(0x80200));
2525

26+
$this->assertSame(
27+
'512.50 KiB',
28+
(new ByteFormatter)->setPrecision(2)->disableAutomaticPrecision()->format(0x80200)
29+
);
30+
2631
$this->assertSame('512.5498 KiB', (new ByteFormatter)->setPrecision(2)->format(0x80233, 4));
2732
}
2833

@@ -31,6 +36,11 @@ public function testOutputFormat()
3136
$this->assertSame('512KiB', (new ByteFormatter)->setFormat('%v%u')->format(0x80000));
3237
}
3338

39+
public function testFixedExponent()
40+
{
41+
$this->assertSame('1024 KiB', (new ByteFormatter)->setFixedExponent(1)->format(1024 * 1024));
42+
}
43+
3444
public function testSymbolDecorator()
3545
{
3646
$this->assertSame(

0 commit comments

Comments
 (0)