Skip to content

Commit c661859

Browse files
allanrenuccicopybara-github
authored andcommitted
Add strings::LegacyPrecision to TSL.
This is a copy of `absl::LegacyPrecision` which is not available in OSS. This allows users to write `absl::StrCat(absl::LegacyPrecision(value))` and preserve the legacy `strings::StrCat(value)` behavior. This is necessary to migrate away from deprecated `strings::StrCat` and `strings::StrAppend`. PiperOrigin-RevId: 755993912
1 parent fb46d42 commit c661859

File tree

3 files changed

+197
-0
lines changed

3 files changed

+197
-0
lines changed

tsl/platform/numbers.cc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,18 @@ size_t FloatToBuffer(float value, char* buffer) {
243243
return snprintf_result;
244244
}
245245

246+
strings_internal::AlphaNumBuffer LegacyPrecision(double d) {
247+
strings_internal::AlphaNumBuffer result;
248+
result.size = DoubleToBuffer(d, result.data.data());
249+
return result;
250+
}
251+
252+
strings_internal::AlphaNumBuffer LegacyPrecision(float f) {
253+
strings_internal::AlphaNumBuffer result;
254+
result.size = FloatToBuffer(f, result.data.data());
255+
return result;
256+
}
257+
246258
std::string FpToString(Fprint fp) {
247259
return absl::StrCat(absl::Hex(fp, absl::kZeroPad16));
248260
}

tsl/platform/numbers.h

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@ limitations under the License.
1616
#ifndef TENSORFLOW_TSL_PLATFORM_NUMBERS_H_
1717
#define TENSORFLOW_TSL_PLATFORM_NUMBERS_H_
1818

19+
#include <array>
1920
#include <cstddef>
2021
#include <cstdint>
2122
#include <string>
2223

2324
#include "absl/base/macros.h"
2425
#include "absl/strings/numbers.h"
26+
#include "absl/strings/string_view.h"
2527
#include "xla/tsl/platform/types.h"
2628
#include "tsl/platform/stringpiece.h"
2729

@@ -77,6 +79,43 @@ size_t FastUInt64ToBufferLeft(uint64_t i, char* buffer); // at least 22 bytes
7779
size_t DoubleToBuffer(double value, char* buffer);
7880
size_t FloatToBuffer(float value, char* buffer);
7981

82+
namespace strings_internal {
83+
// AlphaNumBuffer allows a way to pass a string to absl::StrCat without having
84+
// to do memory allocation. It is simply a pair of a fixed-size character
85+
// array, and a size. Please don't use outside of the "strings" package.
86+
struct AlphaNumBuffer {
87+
std::array<char, kFastToBufferSize> data;
88+
size_t size;
89+
90+
// Support for absl::StrCat() etc.
91+
template <typename Sink>
92+
friend void AbslStringify(Sink& sink, const AlphaNumBuffer& buffer) {
93+
absl::Format(&sink, "%s",
94+
absl::string_view(buffer.data.data(), buffer.size));
95+
}
96+
};
97+
} // namespace strings_internal
98+
99+
// Helper function for legacy google formatting.
100+
template <typename T>
101+
const T& LegacyPrecision(const T& t) {
102+
return t;
103+
}
104+
105+
// Have to use overloads rather than specialization because specialization can't
106+
// change the function return type.
107+
108+
// Helper function for the old strings::StrCat default "float" format, which was
109+
// either %.6g, %.7g, %.8g, or %.9g, basically the smallest string that would
110+
// round-trip back to the original float. This is fast.
111+
strings_internal::AlphaNumBuffer LegacyPrecision(float f);
112+
113+
// Helper function for the old strings::StrCat default "double" format, which
114+
// was either %.15g or %.17g, depending on whether the %.15g format would
115+
// round-trip back to the original double. This is approx. 20-30x slower than
116+
// the others.
117+
strings_internal::AlphaNumBuffer LegacyPrecision(double d);
118+
80119
// Convert a 64-bit fingerprint value to an ASCII representation.
81120
std::string FpToString(Fprint fp);
82121

tsl/platform/numbers_test.cc

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ limitations under the License.
1616
#include "tsl/platform/numbers.h"
1717

1818
#include <cmath>
19+
#include <cstdlib>
20+
#include <limits>
1921
#include <string>
2022

2123
#include "absl/strings/str_cat.h"
@@ -381,5 +383,149 @@ TEST(safe_strtod, Double) {
381383
EXPECT_TRUE(std::isnan(result));
382384
}
383385

386+
std::string DoubleToString(double d) {
387+
return absl::StrCat(strings::LegacyPrecision(d));
388+
}
389+
390+
TEST(LegacyPrecision, SimpleDoubleToString) {
391+
// Make sure that nice, round decimal numbers are printed using few digits,
392+
// even if they can't be represented exactly in binary.
393+
EXPECT_EQ("0", DoubleToString(0.0));
394+
EXPECT_EQ("1", DoubleToString(1.0));
395+
EXPECT_EQ("-1", DoubleToString(-1.0));
396+
EXPECT_EQ("0.2", DoubleToString(0.2));
397+
EXPECT_EQ("1.1", DoubleToString(1.1));
398+
EXPECT_EQ("1e+23", DoubleToString(1e23));
399+
EXPECT_EQ("47.8", DoubleToString(47.8));
400+
EXPECT_EQ("1000.2", DoubleToString(1000.2));
401+
}
402+
403+
void TestDoubleRoundTrip(double value) {
404+
std::string str = DoubleToString(value);
405+
double rt = std::strtod(str.c_str(), nullptr);
406+
if (std::isnan(value)) {
407+
EXPECT_TRUE(std::isnan(rt));
408+
} else {
409+
EXPECT_EQ(value, rt);
410+
}
411+
}
412+
413+
TEST(LegacyPrecision, DoubleRoundTrip) {
414+
// Make sure round-trips with strtod() work. Note that even though we're
415+
// dealing with floating points, we expect the results to be *exactly*
416+
// equal, not approximately.
417+
TestDoubleRoundTrip(1.2345678901234567);
418+
TestDoubleRoundTrip(1.2345678901234565);
419+
TestDoubleRoundTrip(1.2345678901234569);
420+
TestDoubleRoundTrip(47.800000000000001);
421+
TestDoubleRoundTrip(0.10000000000000005);
422+
TestDoubleRoundTrip(0.010000000000000005);
423+
TestDoubleRoundTrip(0.000000010000000000000005);
424+
TestDoubleRoundTrip(1.0000000000000005);
425+
TestDoubleRoundTrip(10.000000000000005);
426+
TestDoubleRoundTrip(100.00000000000005);
427+
TestDoubleRoundTrip(1000.0000000000005);
428+
TestDoubleRoundTrip(100000000000000.05);
429+
430+
// IEEE-754 double finite values furthest from zero.
431+
TestDoubleRoundTrip(1.7976931348623157e308);
432+
TestDoubleRoundTrip(-1.7976931348623157e308);
433+
434+
// IEEE-754 double normalized values closest to zero.
435+
TestDoubleRoundTrip(2.225073858507202e-308);
436+
TestDoubleRoundTrip(-2.225073858507202e-308);
437+
438+
// IEEE-754 double denormalized values closest to zero.
439+
TestDoubleRoundTrip(5e-324);
440+
TestDoubleRoundTrip(-5e-324);
441+
442+
// Biggest and lowest valid numbers.
443+
TestDoubleRoundTrip(std::numeric_limits<double>::max());
444+
TestDoubleRoundTrip(std::numeric_limits<double>::lowest());
445+
446+
// Infinity and NaN.
447+
TestDoubleRoundTrip(std::numeric_limits<double>::infinity());
448+
TestDoubleRoundTrip(-std::numeric_limits<double>::infinity());
449+
TestDoubleRoundTrip(std::numeric_limits<double>::quiet_NaN());
450+
}
451+
452+
std::string FloatToString(float f) {
453+
return absl::StrCat(strings::LegacyPrecision(f));
454+
}
455+
456+
TEST(LegacyPrecision, SimpleFloatToString) {
457+
// Make sure that nice, round decimal numbers are printed using few digits,
458+
// even if they can't be represented exactly in binary.
459+
EXPECT_EQ("0", FloatToString(0.0f));
460+
EXPECT_EQ("-0", FloatToString(-0.0f));
461+
EXPECT_EQ("1", FloatToString(1.0f));
462+
EXPECT_EQ("-1", FloatToString(-1.0f));
463+
EXPECT_EQ("0.2", FloatToString(0.2f));
464+
EXPECT_EQ("1.1", FloatToString(1.1f));
465+
EXPECT_EQ("1e+23", FloatToString(1e23f));
466+
EXPECT_EQ("47.8", FloatToString(47.8f));
467+
EXPECT_EQ("1000.2", FloatToString(1000.2f));
468+
EXPECT_EQ("1.17549435e-38", FloatToString(1.17549435e-38f));
469+
}
470+
471+
void TestFloatRoundTrip(float value) {
472+
std::string str = FloatToString(value);
473+
float rt = std::strtof(str.c_str(), nullptr);
474+
if (std::isnan(value)) {
475+
EXPECT_TRUE(std::isnan(rt));
476+
} else {
477+
EXPECT_EQ(value, rt);
478+
}
479+
}
480+
481+
TEST(LegacyPrecision, FloatRoundTrip) {
482+
// Make sure round-trips with strtod() work. Note that even though we're
483+
// dealing with floating points, we expect the results to be *exactly*
484+
// equal, not approximately.
485+
FloatToString(1.2345678901234567);
486+
FloatToString(1.2345678901234565);
487+
FloatToString(1.2345678901234569);
488+
FloatToString(47.800005);
489+
FloatToString(0.10000005);
490+
FloatToString(0.010000005);
491+
FloatToString(0.000000010000005);
492+
FloatToString(1.0000005);
493+
FloatToString(10.000005);
494+
FloatToString(100.00005);
495+
FloatToString(1000.0005);
496+
FloatToString(100000.05);
497+
FloatToString(10.0000095);
498+
FloatToString(10.0000100);
499+
FloatToString(10.0000105);
500+
FloatToString(10.0000110);
501+
FloatToString(10.0000115);
502+
503+
// IEEE-754 float finite values furthest from zero.
504+
FloatToString(+3.4028234e38f);
505+
FloatToString(-3.4028234e38f);
506+
507+
// IEEE-754 float normalized values closest to zero.
508+
FloatToString(+1.175494351e-38);
509+
FloatToString(-1.175494351e-38);
510+
511+
// IEEE-754 double denormalized values closest to zero.
512+
FloatToString(+1.4e-45);
513+
FloatToString(-1.4e-45);
514+
515+
// Biggest and lowest valid numbers.
516+
FloatToString(std::numeric_limits<float>::max());
517+
FloatToString(std::numeric_limits<float>::lowest());
518+
519+
// Infinity and NaN.
520+
FloatToString(std::numeric_limits<float>::infinity());
521+
FloatToString(-std::numeric_limits<float>::infinity());
522+
FloatToString(std::numeric_limits<float>::quiet_NaN());
523+
}
524+
525+
TEST(LegacyPrecision, NoOpTypes) {
526+
EXPECT_EQ(absl::StrCat(strings::LegacyPrecision(1)), "1");
527+
EXPECT_EQ(absl::StrCat(strings::LegacyPrecision("foo")), "foo");
528+
}
529+
384530
} // namespace strings
385531
} // namespace tsl

0 commit comments

Comments
 (0)