Skip to content

Commit a8d649a

Browse files
Avi Drissmancopybara-github
authored andcommitted
Improve base::ByteCount
- Extend mojo-fication so that it works better in Blink and is available for WebUI - Allow the direct usage in Blink - Fix logging for 0B (it used to log "0GiB") - Fix logging for negative values - Extend logging for the new extended range - Add more tests to cover all new functionality Bug: 429140103 Change-Id: I504738fe67fda188bc12564fccbf0dbb3610ccb4 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6830312 Reviewed-by: Daniel Cheng <[email protected]> Reviewed-by: Nico Weber <[email protected]> Commit-Queue: Avi Drissman <[email protected]> Cr-Commit-Position: refs/heads/main@{#1500250} NOKEYCHECK=True GitOrigin-RevId: b7bb7f0cd06a510b33761cdc2952db8b1dd4b835
1 parent a4f13ec commit a8d649a

8 files changed

+228
-89
lines changed

byte_count.cc

Lines changed: 81 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,42 +4,100 @@
44

55
#include "base/byte_count.h"
66

7-
#include <iomanip>
87
#include <ostream>
98

9+
#include "base/strings/string_number_conversions.h"
10+
1011
namespace base {
1112

1213
std::ostream& operator<<(std::ostream& os, ByteCount byte_count) {
13-
// Save the original stream state
14-
std::ios_base::fmtflags original_flags = os.flags();
15-
std::streamsize original_precision = os.precision();
16-
17-
const int64_t bytes = byte_count.InBytes();
18-
if (bytes % GiB(1).InBytes() == 0) {
19-
os << byte_count.InGiB() << "GiB";
20-
} else if (bytes % MiB(1).InBytes() == 0) {
21-
os << byte_count.InMiB() << "MiB";
22-
} else if (bytes % KiB(1).InBytes() == 0) {
23-
os << byte_count.InKiB() << "KiB";
14+
int64_t bytes = byte_count.InBytes();
15+
16+
// If it's exactly 0 then stream and return.
17+
if (bytes == 0) {
18+
os << "0B";
19+
return os;
20+
}
21+
22+
// If it's exactly INT64_MIN then stream and return. Later in this function,
23+
// negative values are handled by processing their absolute value, but
24+
// INT64_MIN, like all two's complement minimums, has no corresponding
25+
// positive value within range.
26+
if (bytes == std::numeric_limits<int64_t>::min()) {
27+
os << "-8EiB";
28+
return os;
29+
}
30+
31+
// Reserve enough space (e.g. "-1152920504606846976B (-1023.999PiB)" which is
32+
// a full 64-bit negative value with four digits before the decimal).
33+
std::string result;
34+
result.reserve(36);
35+
36+
// Separate out the sign, as it's easier to do magnitude tests on positive
37+
// values.
38+
bool is_negative = bytes < 0;
39+
if (is_negative) {
40+
bytes = -bytes;
41+
byte_count = -byte_count;
42+
result += "-";
43+
}
44+
45+
// If it's an exact number of [EPTGMK]kB then stream that, unless it's a
46+
// quantity measurable by the next magnitude prefix (e.g. if the value is in
47+
// the pebibyte range but it happens to be divisible by 1024 it shouldn't be
48+
// logged in KiB).
49+
if (bytes % EiB(1).InBytes() == 0) {
50+
result += NumberToString(byte_count.InEiB());
51+
result += "EiB";
52+
} else if (bytes % PiB(1).InBytes() == 0 && bytes / EiB(1).InBytes() == 0) {
53+
result += NumberToString(byte_count.InPiB());
54+
result += "PiB";
55+
} else if (bytes % TiB(1).InBytes() == 0 && bytes / PiB(1).InBytes() == 0) {
56+
result += NumberToString(byte_count.InTiB());
57+
result += "TiB";
58+
} else if (bytes % GiB(1).InBytes() == 0 && bytes / TiB(1).InBytes() == 0) {
59+
result += NumberToString(byte_count.InGiB());
60+
result += "GiB";
61+
} else if (bytes % MiB(1).InBytes() == 0 && bytes / GiB(1).InBytes() == 0) {
62+
result += NumberToString(byte_count.InMiB());
63+
result += "MiB";
64+
} else if (bytes % KiB(1).InBytes() == 0 && bytes / MiB(1).InBytes() == 0) {
65+
result += NumberToString(byte_count.InKiB());
66+
result += "KiB";
2467
} else {
25-
os << bytes << "B";
68+
// If not, then stream the exact byte count plus (if larger than 1KiB) an
69+
// estimate for scale.
70+
result += NumberToString(bytes);
71+
result += "B";
2672
if (bytes > KiB(1).InBytes()) {
27-
os << " (" << std::fixed << std::setprecision(3);
28-
if (bytes > GiB(1).InBytes()) {
29-
os << byte_count.InGiBF() << "GiB";
73+
result += " (";
74+
if (is_negative) {
75+
result += "-";
76+
}
77+
if (bytes > EiB(1).InBytes()) {
78+
result += NumberToStringWithFixedPrecision(byte_count.InEiBF(), 3);
79+
result += "EiB";
80+
} else if (bytes > PiB(1).InBytes()) {
81+
result += NumberToStringWithFixedPrecision(byte_count.InPiBF(), 3);
82+
result += "PiB";
83+
} else if (bytes > TiB(1).InBytes()) {
84+
result += NumberToStringWithFixedPrecision(byte_count.InTiBF(), 3);
85+
result += "TiB";
86+
} else if (bytes > GiB(1).InBytes()) {
87+
result += NumberToStringWithFixedPrecision(byte_count.InGiBF(), 3);
88+
result += "GiB";
3089
} else if (bytes > MiB(1).InBytes()) {
31-
os << byte_count.InMiBF() << "MiB";
90+
result += NumberToStringWithFixedPrecision(byte_count.InMiBF(), 3);
91+
result += "MiB";
3292
} else {
33-
os << byte_count.InKiBF() << "KiB";
93+
result += NumberToStringWithFixedPrecision(byte_count.InKiBF(), 3);
94+
result += "KiB";
3495
}
35-
os << ")";
96+
result += ")";
3697
}
3798
}
3899

39-
// Restore the original stream state before returning
40-
os.flags(original_flags);
41-
os.precision(original_precision);
42-
100+
os << result;
43101
return os;
44102
}
45103

byte_count_unittest.cc

Lines changed: 71 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -137,13 +137,25 @@ TEST(ByteCount, InFloating) {
137137
EXPECT_THAT(bytes.InKiBF(), testing::DoubleEq(3355443.19921875));
138138
EXPECT_THAT(bytes.InMiBF(), testing::DoubleEq(3276.7999992370605));
139139
EXPECT_THAT(bytes.InGiBF(), testing::DoubleEq(3.1999999992549419));
140+
constexpr ByteCount morebytes(3435973836343597383);
141+
EXPECT_THAT(morebytes.InTiBF(), testing::DoubleEq(3124999.9995849044));
142+
EXPECT_THAT(morebytes.InPiBF(), testing::DoubleEq(3051.7578120946332));
143+
EXPECT_THAT(morebytes.InEiBF(), testing::DoubleEq(2.9802322383736652));
140144
}
141145

142146
TEST(ByteCountDeathTest, InUnsignedInvalid) {
143147
ByteCount bytes(-2);
144148
BASE_EXPECT_DEATH(bytes.InBytesUnsigned(), "");
145149
}
146150

151+
TEST(ByteCount, UnarySigns) {
152+
ByteCount bytes(42);
153+
EXPECT_EQ(bytes, +bytes);
154+
155+
ByteCount negative_bytes(-42);
156+
EXPECT_EQ(-bytes, negative_bytes);
157+
}
158+
147159
TEST(ByteCount, Arithmetic) {
148160
ByteCount bytes(42);
149161

@@ -156,6 +168,9 @@ TEST(ByteCount, Arithmetic) {
156168
ByteCount mul = bytes * 10;
157169
EXPECT_EQ(420, mul.InBytes());
158170

171+
ByteCount mul2 = 10 * bytes;
172+
EXPECT_EQ(420, mul2.InBytes());
173+
159174
ByteCount div = bytes / 2;
160175
EXPECT_EQ(21, div.InBytes());
161176
}
@@ -217,40 +232,63 @@ TEST(ByteCount, Comparison) {
217232
}
218233

219234
TEST(ByteCount, StreamOperator) {
220-
{
221-
std::stringstream ss;
222-
ss << ByteCount(3);
223-
EXPECT_EQ("3B", ss.str());
224-
}
225-
{
226-
std::stringstream ss;
227-
ss << ByteCount(1024);
228-
EXPECT_EQ("1KiB", ss.str());
229-
}
230-
{
231-
std::stringstream ss;
232-
ss << ByteCount(1025);
233-
EXPECT_EQ("1025B (1.001KiB)", ss.str());
234-
}
235-
{
236-
std::stringstream ss;
237-
ss << ByteCount(1024 * 1024);
238-
EXPECT_EQ("1MiB", ss.str());
239-
}
240-
{
241-
std::stringstream ss;
242-
ss << ByteCount(1024 * 1024 + 1000);
243-
EXPECT_EQ("1049576B (1.001MiB)", ss.str());
244-
}
245-
{
246-
std::stringstream ss;
247-
ss << ByteCount(1024LL * 1024 * 1024);
248-
EXPECT_EQ("1GiB", ss.str());
249-
}
250-
{
235+
struct TestValue {
236+
int64_t bytes;
237+
const char* expected;
238+
} kTestValues[] = {
239+
{-1, "-1B"},
240+
{0, "0B"},
241+
{1, "1B"},
242+
243+
{1024 - 1, "1023B"},
244+
{1024, "1KiB"},
245+
{1024 + 1, "1025B (1.001KiB)"},
246+
{-(1024 - 1), "-1023B"},
247+
{-(1024), "-1KiB"},
248+
{-(1024 + 1), "-1025B (-1.001KiB)"},
249+
250+
{1024 * 1024 - 1, "1048575B (1023.999KiB)"},
251+
{1024 * 1024, "1MiB"},
252+
{1024 * 1024 + 1'000, "1049576B (1.001MiB)"},
253+
{-(1024 * 1024 - 1), "-1048575B (-1023.999KiB)"},
254+
{-(1024 * 1024), "-1MiB"},
255+
{-(1024 * 1024 + 1'000), "-1049576B (-1.001MiB)"},
256+
257+
{1024LL * 1024 * 1024 - 1'000, "1073740824B (1023.999MiB)"},
258+
{1024LL * 1024 * 1024, "1GiB"},
259+
{1024LL * 1024 * 1024 + 1'000'000, "1074741824B (1.001GiB)"},
260+
261+
{1024LL * 1024 * 1024 * 1024 - 1'000'000, "1099510627776B (1023.999GiB)"},
262+
{1024LL * 1024 * 1024 * 1024, "1TiB"},
263+
{1024LL * 1024 * 1024 * 1024 + 1'000'000'000,
264+
"1100511627776B (1.001TiB)"},
265+
266+
{1024LL * 1024 * 1024 * 1024 * 1024 - 1'000'000'000,
267+
"1125898906842624B (1023.999TiB)"},
268+
{1024LL * 1024 * 1024 * 1024 * 1024, "1PiB"},
269+
{1024LL * 1024 * 1024 * 1024 * 1024 + 1'000'000'000'000,
270+
"1126899906842624B (1.001PiB)"},
271+
272+
{1024LL * 1024 * 1024 * 1024 * 1024 * 1024 - 1'000'000'000'000,
273+
"1152920504606846976B (1023.999PiB)"},
274+
{1024LL * 1024 * 1024 * 1024 * 1024 * 1024, "1EiB"},
275+
{1024LL * 1024 * 1024 * 1024 * 1024 * 1024 + 1'000'000'000'000'000,
276+
"1153921504606846976B (1.001EiB)"},
277+
{-(1024LL * 1024 * 1024 * 1024 * 1024 * 1024 - 1'000'000'000'000),
278+
"-1152920504606846976B (-1023.999PiB)"},
279+
{-(1024LL * 1024 * 1024 * 1024 * 1024 * 1024), "-1EiB"},
280+
{-(1024LL * 1024 * 1024 * 1024 * 1024 * 1024 + 1'000'000'000'000'000),
281+
"-1153921504606846976B (-1.001EiB)"},
282+
283+
{ByteCount::Max().InBytes(), "9223372036854775807B (8.000EiB)"},
284+
{std::numeric_limits<int64_t>::min(), "-8EiB"},
285+
{std::numeric_limits<int64_t>::min() + 1,
286+
"-9223372036854775807B (-8.000EiB)"},
287+
};
288+
for (const auto& test_value : kTestValues) {
251289
std::stringstream ss;
252-
ss << ByteCount(1024LL * 1024 * 1024 + 1000000);
253-
EXPECT_EQ("1074741824B (1.001GiB)", ss.str());
290+
ss << ByteCount(test_value.bytes);
291+
EXPECT_EQ(test_value.expected, ss.str());
254292
}
255293
}
256294

strings/string_number_conversions.cc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,13 @@ std::u16string NumberToString16(double value) {
7070
return internal::DoubleToStringT<std::u16string>(value);
7171
}
7272

73+
std::string NumberToStringWithFixedPrecision(double value, int digits) {
74+
return internal::DoubleToStringFixedT<std::string>(value, digits);
75+
}
76+
std::u16string NumberToString16WithFixedPrecision(double value, int digits) {
77+
return internal::DoubleToStringFixedT<std::u16string>(value, digits);
78+
}
79+
7380
bool StringToInt(std::string_view input, int* output) {
7481
return internal::StringToIntImpl(input, *output);
7582
}

strings/string_number_conversions.h

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -50,26 +50,35 @@ BASE_EXPORT std::string NumberToString(long long value);
5050
BASE_EXPORT std::u16string NumberToString16(long long value);
5151
BASE_EXPORT std::string NumberToString(unsigned long long value);
5252
BASE_EXPORT std::u16string NumberToString16(unsigned long long value);
53+
54+
// Returns a string that contains a full representation of `value`.
5355
BASE_EXPORT std::string NumberToString(double value);
5456
BASE_EXPORT std::u16string NumberToString16(double value);
5557

58+
// Returns a string that contains a representation of `value` expressed with
59+
// exactly `digits` digits after the decimal point.
60+
BASE_EXPORT std::string NumberToStringWithFixedPrecision(double value,
61+
int digits);
62+
BASE_EXPORT std::u16string NumberToString16WithFixedPrecision(double value,
63+
int digits);
64+
5665
// String -> number conversions ------------------------------------------------
5766

5867
// Perform a best-effort conversion of the input string to a numeric type,
59-
// setting |*output| to the result of the conversion. Returns true for
68+
// setting `*output` to the result of the conversion. Returns true for
6069
// "perfect" conversions; returns false in the following cases:
61-
// - Overflow. |*output| will be set to the maximum value supported
70+
// - Overflow. `*output` will be set to the maximum value supported
6271
// by the data type.
63-
// - Underflow. |*output| will be set to the minimum value supported
72+
// - Underflow. `*output` will be set to the minimum value supported
6473
// by the data type.
65-
// - Trailing characters in the string after parsing the number. |*output|
74+
// - Trailing characters in the string after parsing the number. `*output`
6675
// will be set to the value of the number that was parsed.
67-
// - Leading whitespace in the string before parsing the number. |*output| will
76+
// - Leading whitespace in the string before parsing the number. `*output` will
6877
// be set to the value of the number that was parsed.
6978
// - No characters parseable as a number at the beginning of the string.
70-
// |*output| will be set to 0.
71-
// - Empty string. |*output| will be set to 0.
72-
// WARNING: Will write to |output| even when returning false.
79+
// `*output` will be set to 0.
80+
// - Empty string. `*output` will be set to 0.
81+
// WARNING: Will write to `output` even when returning false.
7382
// Read the comments above carefully.
7483
BASE_EXPORT bool StringToInt(std::string_view input, int* output);
7584
BASE_EXPORT bool StringToInt(std::u16string_view input, int* output);
@@ -92,18 +101,18 @@ BASE_EXPORT bool StringToSizeT(std::u16string_view input, size_t* output);
92101
// NaN and inf) is undefined. Otherwise, these behave the same as the integral
93102
// variants. This expects the input string to NOT be specific to the locale.
94103
// If your input is locale specific, use ICU to read the number.
95-
// WARNING: Will write to |output| even when returning false.
104+
// WARNING: Will write to `output` even when returning false.
96105
// Read the comments here and above StringToInt() carefully.
97106
BASE_EXPORT bool StringToDouble(std::string_view input, double* output);
98107
BASE_EXPORT bool StringToDouble(std::u16string_view input, double* output);
99108

100109
// Hex encoding ----------------------------------------------------------------
101110

102111
// Returns a hex string representation of a binary buffer. The returned hex
103-
// string will be in upper case. This function does not check if |size| is
112+
// string will be in upper case. This function does not check if `size` is
104113
// within reasonable limits since it's written with trusted data in mind. If
105114
// you suspect that the data you want to format might be large, the absolute
106-
// max size for |size| should be is
115+
// max size for `size` should be is
107116
// std::numeric_limits<size_t>::max() / 2
108117
BASE_EXPORT std::string HexEncode(base::span<const uint8_t> bytes);
109118
BASE_EXPORT std::string HexEncode(std::string_view chars);
@@ -128,42 +137,42 @@ inline void AppendHexEncodedByte(uint8_t byte,
128137
}
129138

130139
// Best effort conversion, see StringToInt above for restrictions.
131-
// Will only successful parse hex values that will fit into |output|, i.e.
132-
// -0x80000000 < |input| < 0x7FFFFFFF.
140+
// Will only successful parse hex values that will fit into `output`, i.e.
141+
// -0x8000'0000 < `input` < 0x7FFF'FFFF.
133142
BASE_EXPORT bool HexStringToInt(std::string_view input, int* output);
134143

135144
// Best effort conversion, see StringToInt above for restrictions.
136-
// Will only successful parse hex values that will fit into |output|, i.e.
137-
// 0x00000000 < |input| < 0xFFFFFFFF.
138-
// The string is not required to start with 0x.
145+
// Will only successful parse hex values that will fit into `output`, i.e.
146+
// 0x0000'0000 < `input` < 0xFFFF'FFFF.
147+
// The string is not required to start with "0x".
139148
BASE_EXPORT bool HexStringToUInt(std::string_view input, uint32_t* output);
140149

141150
// Best effort conversion, see StringToInt above for restrictions.
142-
// Will only successful parse hex values that will fit into |output|, i.e.
143-
// -0x8000000000000000 < |input| < 0x7FFFFFFFFFFFFFFF.
151+
// Will only successful parse hex values that will fit into `output`, i.e.
152+
// -0x8000'0000'0000'0000 < `input` < 0x7FFF'FFFF'FFFF'FFFF.
144153
BASE_EXPORT bool HexStringToInt64(std::string_view input, int64_t* output);
145154

146155
// Best effort conversion, see StringToInt above for restrictions.
147-
// Will only successful parse hex values that will fit into |output|, i.e.
148-
// 0x0000000000000000 < |input| < 0xFFFFFFFFFFFFFFFF.
149-
// The string is not required to start with 0x.
156+
// Will only successful parse hex values that will fit into `output`, i.e.
157+
// 0x0000'0000'0000'0000 < `input` < 0xFFFF'FFFF'FFFF'FFFF.
158+
// The string is not required to start with "0x".
150159
BASE_EXPORT bool HexStringToUInt64(std::string_view input, uint64_t* output);
151160

152161
// Similar to the previous functions, except that output is a vector of bytes.
153-
// |*output| will contain as many bytes as were successfully parsed prior to the
162+
// `*output` will contain as many bytes as were successfully parsed prior to the
154163
// error. There is no overflow, but input.size() must be evenly divisible by 2.
155-
// Leading 0x or +/- are not allowed.
164+
// Leading "0x" or +/- are not allowed.
156165
BASE_EXPORT bool HexStringToBytes(std::string_view input,
157166
std::vector<uint8_t>* output);
158167

159168
// Same as HexStringToBytes, but for an std::string.
160169
BASE_EXPORT bool HexStringToString(std::string_view input, std::string* output);
161170

162-
// Decodes the hex string |input| into a presized |output|. The output buffer
163-
// must be sized exactly to |input.size() / 2| or decoding will fail and no
164-
// bytes will be written to |output|. Decoding an empty input is also
171+
// Decodes the hex string `input` into a presized `output`. The output buffer
172+
// must be sized exactly to `input.size() / 2` or decoding will fail and no
173+
// bytes will be written to `output`. Decoding an empty input is also
165174
// considered a failure. When decoding fails due to encountering invalid input
166-
// characters, |output| will have been filled with the decoded bytes up until
175+
// characters, `output` will have been filled with the decoded bytes up until
167176
// the failure.
168177
BASE_EXPORT bool HexStringToSpan(std::string_view input,
169178
base::span<uint8_t> output);

0 commit comments

Comments
 (0)