Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions absl/strings/numbers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <array>
#include <cassert>
#include <cfloat> // for DBL_DIG and FLT_DIG
#include <clocale> // for localeconv
#include <cmath> // for HUGE_VAL
#include <cstdint>
#include <cstdio>
Expand Down Expand Up @@ -448,6 +449,18 @@ char* absl_nonnull numbers_internal::RoundTripDoubleToBuffer(
ABSL_ASSERT(snprintf_result > 0 &&
snprintf_result < numbers_internal::kFastToBufferSize);
}

// snprintf() writes the radix character chosen by the global C locale's
// LC_NUMERIC category, so a process that has called setlocale() can end up
// with a separator other than '.' here. The rest of Abseil's float
// formatting (RoundTripFloatToBuffer, SixDigitsToBuffer) is locale-
// independent and SimpleAtod() only accepts '.', so rewrite the radix back
// to '.' to keep absl::HighPrecision(double) locale-independent and
// round-trippable through SimpleAtod().
const char radix = *localeconv()->decimal_point;
if (radix != '.') {
if (char* p = strchr(buffer, radix)) *p = '.';
}
return buffer;
}

Expand Down
31 changes: 31 additions & 0 deletions absl/strings/numbers_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <cfloat>
#include <cinttypes>
#include <climits>
#include <clocale>
#include <cmath>
#include <cstddef>
#include <cstdint>
Expand Down Expand Up @@ -1717,6 +1718,36 @@ class SimpleDtoaTest : public testing::Test {
fenv_t fp_env_;
};

TEST(SimpleDtoa, HighPrecisionIsLocaleIndependent) {
// absl::HighPrecision(double) routes through RoundTripDoubleToBuffer(), which
// used to leak the global C locale's radix character (e.g. ',' under de_DE)
// into its output. HighPrecision() promises a value that SimpleAtod() reads
// back exactly, and SimpleAtod() only accepts '.', so the radix must stay '.'
// regardless of the active locale.
std::string old_locale = setlocale(LC_NUMERIC, nullptr);
const char* comma_locales[] = {"de_DE.UTF-8", "de_DE", "fr_FR.UTF-8",
"fr_FR", "nl_NL.UTF-8"};
bool changed = false;
for (const char* loc : comma_locales) {
if (setlocale(LC_NUMERIC, loc) != nullptr) {
changed = true;
break;
}
}
if (!changed) {
GTEST_SKIP() << "No comma-radix locale available on this system.";
}
EXPECT_EQ(absl::StrCat(absl::HighPrecision(0.5)), "0.5");
EXPECT_EQ(absl::StrCat(absl::HighPrecision(-1.25)), "-1.25");
EXPECT_EQ(absl::StrCat(absl::HighPrecision(3.14159265358979)),
"3.14159265358979");
double parsed = 0;
EXPECT_TRUE(
absl::SimpleAtod(absl::StrCat(absl::HighPrecision(0.1)), &parsed));
EXPECT_EQ(parsed, 0.1);
setlocale(LC_NUMERIC, old_locale.c_str());
}

// Run the given runnable functor for "cases" test cases, chosen over the
// available range of float. pi and e and 1/e are seeded, and then all
// available integer powers of 2 and 10 are multiplied against them. In
Expand Down