736 changes: 736 additions & 0 deletions libc/src/__support/detailed_powers_of_ten.h

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions libc/src/__support/high_precision_decimal.h
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ class HighPrecsisionDecimal {
sawDigit = true;
if (*numString == '0' && this->numDigits == 0) {
--this->decimalPoint;
++numString;
continue;
}
if (this->numDigits < MAX_NUM_DIGITS) {
Expand All @@ -311,6 +312,11 @@ class HighPrecsisionDecimal {
++numString;
if (isdigit(*numString) || *numString == '+' || *numString == '-') {
int32_t addToExp = strtointeger<int32_t>(numString, nullptr, 10);
if (addToExp > 100000) {
addToExp = 100000;
} else if (addToExp < -100000) {
addToExp = -100000;
}
this->decimalPoint += addToExp;
}
}
Expand Down
593 changes: 593 additions & 0 deletions libc/src/__support/str_to_float.h

Large diffs are not rendered by default.

30 changes: 30 additions & 0 deletions libc/src/stdlib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ add_entrypoint_object(
libc.src.__support.str_conv_utils
)

add_entrypoint_object(
atof
SRCS
atof.cpp
HDRS
atof.h
DEPENDS
libc.src.__support.str_conv_utils
)

add_entrypoint_object(
atol
SRCS
Expand All @@ -28,6 +38,26 @@ add_entrypoint_object(
libc.src.__support.str_conv_utils
)

add_entrypoint_object(
strtof
SRCS
strtof.cpp
HDRS
strtof.h
DEPENDS
libc.src.__support.str_conv_utils
)

add_entrypoint_object(
strtod
SRCS
strtod.cpp
HDRS
strtod.h
DEPENDS
libc.src.__support.str_conv_utils
)

add_entrypoint_object(
strtol
SRCS
Expand Down
19 changes: 19 additions & 0 deletions libc/src/stdlib/atof.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//===-- Implementation of atof --------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "src/stdlib/atof.h"
#include "src/__support/common.h"
#include "src/__support/str_to_float.h"

namespace __llvm_libc {

LLVM_LIBC_FUNCTION(double, atof, (const char *str)) {
return internal::strtofloatingpoint<double>(str, nullptr);
}

} // namespace __llvm_libc
18 changes: 18 additions & 0 deletions libc/src/stdlib/atof.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//===-- Implementation header for atof --------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_LIBC_SRC_STDLIB_ATOF_H
#define LLVM_LIBC_SRC_STDLIB_ATOF_H

namespace __llvm_libc {

double atof(const char *str);

} // namespace __llvm_libc

#endif // LLVM_LIBC_SRC_STDLIB_ATOI_H
20 changes: 20 additions & 0 deletions libc/src/stdlib/strtod.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//===-- Implementation of strtod ------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "src/stdlib/strtod.h"
#include "src/__support/common.h"
#include "src/__support/str_to_float.h"

namespace __llvm_libc {

LLVM_LIBC_FUNCTION(double, strtod,
(const char *__restrict str, char **__restrict str_end)) {
return internal::strtofloatingpoint<double>(str, str_end);
}

} // namespace __llvm_libc
18 changes: 18 additions & 0 deletions libc/src/stdlib/strtod.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//===-- Implementation header for strtod ------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_LIBC_SRC_STDLIB_STRTOD_H
#define LLVM_LIBC_SRC_STDLIB_STRTOD_H

namespace __llvm_libc {

double strtod(const char *__restrict str, char **__restrict str_end);

} // namespace __llvm_libc

#endif // LLVM_LIBC_SRC_STDLIB_STRTOD_H
20 changes: 20 additions & 0 deletions libc/src/stdlib/strtof.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//===-- Implementation of strtof ------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "src/stdlib/strtof.h"
#include "src/__support/common.h"
#include "src/__support/str_to_float.h"

namespace __llvm_libc {

LLVM_LIBC_FUNCTION(float, strtof,
(const char *__restrict str, char **__restrict str_end)) {
return internal::strtofloatingpoint<float>(str, str_end);
}

} // namespace __llvm_libc
18 changes: 18 additions & 0 deletions libc/src/stdlib/strtof.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//===-- Implementation header for strtof ------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_LIBC_SRC_STDLIB_STRTOF_H
#define LLVM_LIBC_SRC_STDLIB_STRTOF_H

namespace __llvm_libc {

float strtof(const char *__restrict str, char **__restrict str_end);

} // namespace __llvm_libc

#endif // LLVM_LIBC_SRC_STDLIB_STRTOF_H
29 changes: 29 additions & 0 deletions libc/test/src/__support/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,32 @@ add_libc_unittest(
DEPENDS
libc.src.__support.high_precision_decimal
)

add_libc_unittest(
str_to_float_test
SUITE
libc_support_unittests
SRCS
str_to_float_test.cpp
DEPENDS
libc.src.__support.str_conv_utils
)

add_executable(
libc_str_to_float_comparison_test
str_to_float_comparison_test.cpp
)

target_link_libraries(libc_str_to_float_comparison_test
PRIVATE
llvmlibc
)

set(float_test_file ${CMAKE_CURRENT_SOURCE_DIR}/str_to_float_comparison_data.txt)

add_custom_command(TARGET libc_str_to_float_comparison_test
POST_BUILD
COMMAND $<TARGET_FILE:libc_str_to_float_comparison_test> ${float_test_file}
DEPENDS ${float_test_file}
COMMENT "Test the strtof and strtod implementations against precomputed results."
VERBATIM)
7 changes: 7 additions & 0 deletions libc/test/src/__support/str_to_float_comparison_data.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# This is the sample data from https://github.com/nigeltao/parse-number-fxx-test-data
3C00 3F800000 3FF0000000000000 1
3D00 3FA00000 3FF4000000000000 1.25
3D9A 3FB33333 3FF6666666666666 1.4
57B7 42F6E979 405EDD2F1A9FBE77 123.456
622A 44454000 4088A80000000000 789
7C00 7F800000 7FF0000000000000 123.456e789
172 changes: 172 additions & 0 deletions libc/test/src/__support/str_to_float_comparison_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
//===-- strtofloatingpoint comparison test --------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// #include "src/__support/str_float_conv_utils.h"

#include <stdlib.h>

// #include "src/__support/FPUtil/FPBits.h"

#include <fstream>
#include <iostream>
#include <string>

// The intent of this test is to read in files in the format used in this test
// dataset: https://github.com/nigeltao/parse-number-fxx-test-data
// The format is as follows:
// Hexadecimal representations of IEEE754 floats in 16 bits, 32 bits, and 64
// bits, then the string that matches to them.

// 3C00 3F800000 3FF0000000000000 1.0

// By default, float_comp_in.txt is used as the test set, but once built this
// file can be run against the larger test set. To do that, clone the repository
// with the dataset, then navigate to the compiled binary of this file (it
// should be in llvm_project/build/bin). Run the following command:
// ./libc_str_to_float_comparison_test <path/to/dataset/repo>/data/*
// It will take a few seconds to run.

static inline uint32_t hexCharToU32(char in) {
return in > '9' ? in + 10 - 'A' : in - '0';
}

// Fast because it assumes inStr points to exactly 8 uppercase hex chars
static inline uint32_t fastHexToU32(const char *inStr) {
uint32_t result = 0;
result = (hexCharToU32(inStr[0]) << 28) + (hexCharToU32(inStr[1]) << 24) +
(hexCharToU32(inStr[2]) << 20) + (hexCharToU32(inStr[3]) << 16) +
(hexCharToU32(inStr[4]) << 12) + (hexCharToU32(inStr[5]) << 8) +
(hexCharToU32(inStr[6]) << 4) + hexCharToU32(inStr[7]);
return result;
}

// Fast because it assumes inStr points to exactly 8 uppercase hex chars
static inline uint64_t fastHexToU64(const char *inStr) {
uint64_t result = 0;
result = (static_cast<uint64_t>(fastHexToU32(inStr)) << 32) +
fastHexToU32(inStr + 8);
return result;
}

int checkFile(char *inputFileName, int *totalFails, int *totalBitDiffs,
int *detailedBitDiffs, int *total) {
int32_t curFails = 0; // Only counts actual failures, not bitdiffs.
int32_t curBitDiffs = 0; // A bitdiff is when the expected result and actual
// result are off by +/- 1 bit.
std::string line;
std::string num;

std::ifstream fileStream(inputFileName, std::ifstream::in);

if (!fileStream.is_open()) {
std::cout << "file '" << inputFileName << "' failed to open. Exiting.\n";
return 1;
}
while (getline(fileStream, line)) {
if (line[0] == '#') {
continue;
}
*total = *total + 1;
uint32_t expectedFloatRaw;
uint64_t expectedDoubleRaw;

expectedFloatRaw = fastHexToU32(line.c_str() + 5);
expectedDoubleRaw = fastHexToU64(line.c_str() + 14);
num = line.substr(31);

float floatResult = strtof(num.c_str(), nullptr);

double doubleResult = strtod(num.c_str(), nullptr);

uint32_t floatRaw = *(uint32_t *)(&floatResult);

uint64_t doubleRaw = *(uint64_t *)(&doubleResult);

if (!(expectedFloatRaw == floatRaw)) {
if (expectedFloatRaw == floatRaw + 1 ||
expectedFloatRaw == floatRaw - 1) {
curBitDiffs++;
if (expectedFloatRaw == floatRaw + 1) {
detailedBitDiffs[0] = detailedBitDiffs[0] + 1; // float low
} else {
detailedBitDiffs[1] = detailedBitDiffs[1] + 1; // float high
}
} else {
curFails++;
}
if (curFails + curBitDiffs < 10) {
std::cout << "Float fail for '" << num << "'. Expected " << std::hex
<< expectedFloatRaw << " but got " << floatRaw << "\n"
<< std::dec;
}
}

if (!(expectedDoubleRaw == doubleRaw)) {
if (expectedDoubleRaw == doubleRaw + 1 ||
expectedDoubleRaw == doubleRaw - 1) {
curBitDiffs++;
if (expectedDoubleRaw == doubleRaw + 1) {
detailedBitDiffs[2] = detailedBitDiffs[2] + 1; // double low
} else {
detailedBitDiffs[3] = detailedBitDiffs[3] + 1; // double high
}
} else {
curFails++;
}
if (curFails + curBitDiffs < 10) {
std::cout << "Double fail for '" << num << "'. Expected " << std::hex
<< expectedDoubleRaw << " but got " << doubleRaw << "\n"
<< std::dec;
}
}
}

fileStream.close();

*totalBitDiffs += curBitDiffs;
*totalFails += curFails;

if (curFails > 1 || curBitDiffs > 1) {
return 2;
}
return 0;
}

int main(int argc, char *argv[]) {
int result = 0;
int fails = 0;

// Bitdiffs are cases where the expected result and actual result only differ
// by +/- the least significant bit. They are tracked seperately from larger
// failures since a bitdiff is most likely the result of a rounding error, and
// splitting them off makes them easier to track down.
int bitdiffs = 0;
int detailedBitDiffs[4] = {0, 0, 0, 0};

int total = 0;
for (int i = 1; i < argc; i++) {
std::cout << "Starting file " << argv[i] << "\n";
int curResult =
checkFile(argv[i], &fails, &bitdiffs, detailedBitDiffs, &total);
if (curResult == 1) {
result = 1;
break;
} else if (curResult == 2) {
result = 2;
}
}
std::cout << "Results:\n"
<< "Total significant failed conversions: " << fails << "\n"
<< "Total conversions off by +/- 1 bit: " << bitdiffs << "\n"
<< "\t" << detailedBitDiffs[0] << "\tfloat low\n"
<< "\t" << detailedBitDiffs[1] << "\tfloat high\n"
<< "\t" << detailedBitDiffs[2] << "\tdouble low\n"
<< "\t" << detailedBitDiffs[3] << "\tdouble high\n"
<< "Total lines: " << total << "\n";
return result;
}
200 changes: 200 additions & 0 deletions libc/test/src/__support/str_to_float_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
//===-- Unittests for str_to_float ----------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "src/__support/FPUtil/FPBits.h"
#include "src/__support/str_to_float.h"

#include "utils/UnitTest/Test.h"

class LlvmLibcStrToFloatTest : public __llvm_libc::testing::Test {
public:
template <class T>
void EiselLemireTest(
const typename __llvm_libc::fputil::FPBits<T>::UIntType inputMantissa,
const int32_t inputExp10,
const typename __llvm_libc::fputil::FPBits<T>::UIntType
expectedOutputMantissa,
const uint32_t expectedOutputExp2) {
typename __llvm_libc::fputil::FPBits<T>::UIntType actualOutputMantissa = 0;
uint32_t actualOutputExp2 = 0;

ASSERT_TRUE(__llvm_libc::internal::eiselLemire<T>(
inputMantissa, inputExp10, &actualOutputMantissa, &actualOutputExp2));
EXPECT_EQ(actualOutputMantissa, expectedOutputMantissa);
EXPECT_EQ(actualOutputExp2, expectedOutputExp2);
}

template <class T>
void SimpleDecimalConversionTest(
const char *__restrict numStart,
const typename __llvm_libc::fputil::FPBits<T>::UIntType
expectedOutputMantissa,
const uint32_t expectedOutputExp2, const int expectedErrno = 0) {
typename __llvm_libc::fputil::FPBits<T>::UIntType actualOutputMantissa = 0;
uint32_t actualOutputExp2 = 0;
errno = 0;

__llvm_libc::internal::simpleDecimalConversion<T>(
numStart, &actualOutputMantissa, &actualOutputExp2);
EXPECT_EQ(actualOutputMantissa, expectedOutputMantissa);
EXPECT_EQ(actualOutputExp2, expectedOutputExp2);
EXPECT_EQ(errno, expectedErrno);
}
};

TEST(LlvmLibcStrToFloatTest, LeadingZeroes) {
uint64_t testNum64 = 1;
uint32_t numOfZeroes = 63;
EXPECT_EQ(__llvm_libc::internal::leadingZeroes<uint64_t>(0), 64u);
for (; numOfZeroes < 64; testNum64 <<= 1, numOfZeroes--) {
EXPECT_EQ(__llvm_libc::internal::leadingZeroes<uint64_t>(testNum64),
numOfZeroes);
}

testNum64 = 3;
numOfZeroes = 62;
for (; numOfZeroes > 63; testNum64 <<= 1, numOfZeroes--) {
EXPECT_EQ(__llvm_libc::internal::leadingZeroes<uint64_t>(testNum64),
numOfZeroes);
}

EXPECT_EQ(__llvm_libc::internal::leadingZeroes<uint64_t>(0xffffffffffffffff),
0u);

testNum64 = 1;
numOfZeroes = 63;
for (; numOfZeroes > 63; testNum64 = (testNum64 << 1) + 1, numOfZeroes--) {
EXPECT_EQ(__llvm_libc::internal::leadingZeroes<uint64_t>(testNum64),
numOfZeroes);
}

uint64_t testNum32 = 1;
numOfZeroes = 31;
EXPECT_EQ(__llvm_libc::internal::leadingZeroes<uint32_t>(0), 32u);
for (; numOfZeroes < 32; testNum32 <<= 1, numOfZeroes--) {
EXPECT_EQ(__llvm_libc::internal::leadingZeroes<uint32_t>(testNum32),
numOfZeroes);
}

EXPECT_EQ(__llvm_libc::internal::leadingZeroes<uint32_t>(0xffffffff), 0u);
}

TEST_F(LlvmLibcStrToFloatTest, EiselLemireFloat64Simple) {
EiselLemireTest<double>(12345678901234567890u, 1, 0x1AC53A7E04BCDA, 1089);
EiselLemireTest<double>(123, 0, 0x1EC00000000000, 1029);
EiselLemireTest<double>(12345678901234568192u, 0, 0x156A95319D63E2, 1086);
}

TEST_F(LlvmLibcStrToFloatTest, EiselLemireFloat64SpecificFailures) {
// These test cases have caused failures in the past.
EiselLemireTest<double>(358416272, -33, 0x1BBB2A68C9D0B9, 941);
EiselLemireTest<double>(2166568064000000238u, -9, 0x10246690000000, 1054);
EiselLemireTest<double>(2794967654709307187u, 1, 0x183e132bc608c8, 1087);
EiselLemireTest<double>(2794967654709307188u, 1, 0x183e132bc608c9, 1087);
}

TEST_F(LlvmLibcStrToFloatTest, EiselLemireFallbackStates) {
// Check the fallback states for the algorithm:
uint32_t floatOutputMantissa = 0;
uint64_t doubleOutputMantissa = 0;
__uint128_t tooLongMantissa = 0;
uint32_t outputExp2 = 0;

// This Eisel-Lemire implementation doesn't support long doubles yet.
ASSERT_FALSE(__llvm_libc::internal::eiselLemire<long double>(
tooLongMantissa, 0, &tooLongMantissa, &outputExp2));

// This number can't be evaluated by Eisel-Lemire since it's exactly 1024 away
// from both of its closest floating point approximations
// (12345678901234548736 and 12345678901234550784)
ASSERT_FALSE(__llvm_libc::internal::eiselLemire<double>(
12345678901234549760u, 0, &doubleOutputMantissa, &outputExp2));

ASSERT_FALSE(__llvm_libc::internal::eiselLemire<float>(
20040229, 0, &floatOutputMantissa, &outputExp2));
}

TEST_F(LlvmLibcStrToFloatTest, SimpleDecimalConversion64BasicWholeNumbers) {
SimpleDecimalConversionTest<double>("123456789012345678900", 0x1AC53A7E04BCDA,
1089);
SimpleDecimalConversionTest<double>("123", 0x1EC00000000000, 1029);
SimpleDecimalConversionTest<double>("12345678901234549760", 0x156A95319D63D8,
1086);
}

TEST_F(LlvmLibcStrToFloatTest, SimpleDecimalConversion64BasicDecimals) {
SimpleDecimalConversionTest<double>("1.2345", 0x13c083126e978d, 1023);
SimpleDecimalConversionTest<double>(".2345", 0x1e04189374bc6a, 1020);
SimpleDecimalConversionTest<double>(".299792458", 0x132fccb4aca314, 1021);
}

TEST_F(LlvmLibcStrToFloatTest, SimpleDecimalConversion64BasicExponents) {
SimpleDecimalConversionTest<double>("1e10", 0x12a05f20000000, 1056);
SimpleDecimalConversionTest<double>("1e-10", 0x1b7cdfd9d7bdbb, 989);
SimpleDecimalConversionTest<double>("1e300", 0x17e43c8800759c, 2019);
SimpleDecimalConversionTest<double>("1e-300", 0x156e1fc2f8f359, 26);
}

TEST_F(LlvmLibcStrToFloatTest, SimpleDecimalConversion64BasicSubnormals) {
SimpleDecimalConversionTest<double>("1e-320", 0x7e8, 0);
SimpleDecimalConversionTest<double>("1e-308", 0x730d67819e8d2, 0);
SimpleDecimalConversionTest<double>("2.9e-308", 0x14da6df5e4bcc8, 1);
}

TEST_F(LlvmLibcStrToFloatTest, SimpleDecimalConversion64SubnormalRounding) {

// Technically you can keep adding digits until you hit the truncation limit,
// but this is the shortest string that results in the maximum subnormal that
// I found.
SimpleDecimalConversionTest<double>("2.225073858507201e-308", 0xfffffffffffff,
0);

// Same here, if you were to extend the max subnormal out for another 800
// digits, incrementing any one of those digits would create a normal number.
SimpleDecimalConversionTest<double>("2.2250738585072012e-308",
0x10000000000000, 1);
}

TEST_F(LlvmLibcStrToFloatTest, SimpleDecimalConversion32SpecificFailures) {
SimpleDecimalConversionTest<float>(
"1.4012984643248170709237295832899161312802619418765e-45", 0x1, 0);
}

TEST(LlvmLibcStrToFloatTest, SimpleDecimalConversionExtraTypes) {
uint32_t floatOutputMantissa = 0;
uint32_t outputExp2 = 0;

errno = 0;
__llvm_libc::internal::simpleDecimalConversion<float>(
"123456789012345678900", &floatOutputMantissa, &outputExp2);
EXPECT_EQ(floatOutputMantissa, uint32_t(0xd629d4));
EXPECT_EQ(outputExp2, uint32_t(193));
EXPECT_EQ(errno, 0);

uint64_t doubleOutputMantissa = 0;
outputExp2 = 0;

errno = 0;
__llvm_libc::internal::simpleDecimalConversion<double>(
"123456789012345678900", &doubleOutputMantissa, &outputExp2);
EXPECT_EQ(doubleOutputMantissa, uint64_t(0x1AC53A7E04BCDA));
EXPECT_EQ(outputExp2, uint32_t(1089));
EXPECT_EQ(errno, 0);

// TODO(michaelrj): Get long double support working.

// __uint128_t longDoubleOutputMantissa = 0;
// outputExp2 = 0;

// errno = 0;
// __llvm_libc::internal::simpleDecimalConversion<long double>(
// "123456789012345678900", &longDoubleOutputMantissa, &outputExp2);
// EXPECT_EQ(longDoubleOutputMantissa, __uint128_t(0x1AC53A7E04BCDA));
// EXPECT_EQ(outputExp2, uint32_t(1089));
// EXPECT_EQ(errno, 0);
}
30 changes: 30 additions & 0 deletions libc/test/src/stdlib/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
add_libc_testsuite(libc_stdlib_unittests)

add_libc_unittest(
atof_test
SUITE
libc_stdlib_unittests
SRCS
atof_test.cpp
DEPENDS
libc.src.stdlib.atof
)

add_libc_unittest(
atoi_test
SUITE
Expand Down Expand Up @@ -30,6 +40,26 @@ add_libc_unittest(
libc.src.stdlib.atoll
)

add_libc_unittest(
strtod_test
SUITE
libc_stdlib_unittests
SRCS
strtod_test.cpp
DEPENDS
libc.src.stdlib.strtod
)

add_libc_unittest(
strtof_test
SUITE
libc_stdlib_unittests
SRCS
strtof_test.cpp
DEPENDS
libc.src.stdlib.strtof
)

add_libc_unittest(
strtol_test
SUITE
Expand Down
52 changes: 52 additions & 0 deletions libc/test/src/stdlib/atof_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//===-- Unittests for atof ------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "src/__support/FPUtil/FPBits.h"
#include "src/stdlib/atof.h"

#include "utils/UnitTest/Test.h"

#include <errno.h>
#include <limits.h>
#include <stddef.h>

// This is just a simple test to make sure that this function works at all. It's
// functionally identical to strtod so the bulk of the testing is there.
TEST(LlvmLibcAToFTest, SimpleTest) {
__llvm_libc::fputil::FPBits<double> expectedFP =
__llvm_libc::fputil::FPBits<double>(uint64_t(0x405ec00000000000));

errno = 0;
double result = __llvm_libc::atof("123");

__llvm_libc::fputil::FPBits<double> actualFP =
__llvm_libc::fputil::FPBits<double>(result);

EXPECT_EQ(actualFP.bits, expectedFP.bits);
EXPECT_EQ(actualFP.getSign(), expectedFP.getSign());
EXPECT_EQ(actualFP.getExponent(), expectedFP.getExponent());
EXPECT_EQ(actualFP.getMantissa(), expectedFP.getMantissa());
EXPECT_EQ(errno, 0);
}

TEST(LlvmLibcAToFTest, FailedParsingTest) {
__llvm_libc::fputil::FPBits<double> expectedFP =
__llvm_libc::fputil::FPBits<double>(uint64_t(0));

errno = 0;
double result = __llvm_libc::atof("???");

__llvm_libc::fputil::FPBits<double> actualFP =
__llvm_libc::fputil::FPBits<double>(result);

EXPECT_EQ(actualFP.bits, expectedFP.bits);
EXPECT_EQ(actualFP.getSign(), expectedFP.getSign());
EXPECT_EQ(actualFP.getExponent(), expectedFP.getExponent());
EXPECT_EQ(actualFP.getMantissa(), expectedFP.getMantissa());
EXPECT_EQ(errno, 0);
}
90 changes: 90 additions & 0 deletions libc/test/src/stdlib/strtod_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//===-- Unittests for strtod ---------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "src/__support/FPUtil/FPBits.h"
#include "src/stdlib/strtod.h"

#include "utils/UnitTest/Test.h"

#include <errno.h>
#include <limits.h>
#include <stddef.h>

class LlvmLibcStrToDTest : public __llvm_libc::testing::Test {
public:
void runTest(const char *inputString, const ptrdiff_t expectedStrLen,
const uint64_t expectedRawData, const int expectedErrno = 0) {
// expectedRawData is the expected double result as a uint64_t, organized
// according to IEEE754:
//
// +-- 1 Sign Bit +-- 52 Mantissa bits
// | |
// | +-------------------------+------------------------+
// | | |
// SEEEEEEEEEEEMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
// | |
// +----+----+
// |
// +-- 11 Exponent Bits
//
// This is so that the result can be compared in parts.
char *strEnd = nullptr;

__llvm_libc::fputil::FPBits<double> expectedFP =
__llvm_libc::fputil::FPBits<double>(expectedRawData);

errno = 0;
double result = __llvm_libc::strtod(inputString, &strEnd);

__llvm_libc::fputil::FPBits<double> actualFP =
__llvm_libc::fputil::FPBits<double>(result);

EXPECT_EQ(strEnd - inputString, expectedStrLen);

EXPECT_EQ(actualFP.bits, expectedFP.bits);
EXPECT_EQ(actualFP.getSign(), expectedFP.getSign());
EXPECT_EQ(actualFP.getExponent(), expectedFP.getExponent());
EXPECT_EQ(actualFP.getMantissa(), expectedFP.getMantissa());
EXPECT_EQ(errno, expectedErrno);
}
};

TEST_F(LlvmLibcStrToDTest, SimpleTest) {
runTest("123", 3, uint64_t(0x405ec00000000000));

// This should fail on Eisel-Lemire, forcing a fallback to simple decimal
// conversion.
runTest("12345678901234549760", 20, uint64_t(0x43e56a95319d63d8));

// Found while looking for difficult test cases here:
// https://github.com/nigeltao/parse-number-fxx-test-data/blob/main/more-test-cases/golang-org-issue-36657.txt
runTest("1090544144181609348835077142190", 31, uint64_t(0x462b8779f2474dfb));

runTest("0x123", 5, uint64_t(0x4072300000000000));
}

// These are tests that have caused problems in the past.
TEST_F(LlvmLibcStrToDTest, SpecificFailures) {
runTest("3E70000000000000", 16, uint64_t(0x7FF0000000000000), ERANGE);
runTest("358416272e-33", 13, uint64_t(0x3adbbb2a68c9d0b9));
runTest("2.16656806400000023841857910156251e9", 36,
uint64_t(0x41e0246690000001));
runTest("27949676547093071875", 20, uint64_t(0x43f83e132bc608c9));
}

TEST_F(LlvmLibcStrToDTest, FuzzFailures) {
runTest("-\xff\xff\xff\xff\xff\xff\xff\x01", 0, uint64_t(0));
runTest("-.????", 0, uint64_t(0));
runTest("44444444444444444444444444444444444444444444444444A44444444444444444"
"44444444444*\x99\xff\xff\xff\xff",
50, uint64_t(0x4a3e68fdd0e0b2d8));
runTest("-NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNKNNNNNNNNNNNNNNNNNN?"
"NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN?",
0, uint64_t(0));
runTest("0x.666E40", 9, uint64_t(0x3fd99b9000000000));
}
158 changes: 158 additions & 0 deletions libc/test/src/stdlib/strtof_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
//===-- Unittests for strtof ---------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "src/__support/FPUtil/FPBits.h"
#include "src/stdlib/strtof.h"

#include "utils/UnitTest/Test.h"

#include <errno.h>
#include <limits.h>
#include <stddef.h>

class LlvmLibcStrToFTest : public __llvm_libc::testing::Test {
public:
void runTest(const char *inputString, const ptrdiff_t expectedStrLen,
const uint32_t expectedRawData, const int expectedErrno = 0) {
// expectedRawData is the expected float result as a uint32_t, organized
// according to IEEE754:
//
// +-- 1 Sign Bit +-- 23 Mantissa bits
// | |
// | +----------+----------+
// | | |
// SEEEEEEEEMMMMMMMMMMMMMMMMMMMMMMM
// | |
// +--+---+
// |
// +-- 8 Exponent Bits
//
// This is so that the result can be compared in parts.
char *strEnd = nullptr;

__llvm_libc::fputil::FPBits<float> expectedFP =
__llvm_libc::fputil::FPBits<float>(expectedRawData);

errno = 0;
float result = __llvm_libc::strtof(inputString, &strEnd);

__llvm_libc::fputil::FPBits<float> actualFP =
__llvm_libc::fputil::FPBits<float>(result);

EXPECT_EQ(strEnd - inputString, expectedStrLen);

EXPECT_EQ(actualFP.bits, expectedFP.bits);
EXPECT_EQ(actualFP.getSign(), expectedFP.getSign());
EXPECT_EQ(actualFP.getExponent(), expectedFP.getExponent());
EXPECT_EQ(actualFP.getMantissa(), expectedFP.getMantissa());
EXPECT_EQ(errno, expectedErrno);
}
};

// This is the set of tests that I have working (verified correct when compared
// to system libc). This is here so I don't break more things when I try to fix
// them.

TEST_F(LlvmLibcStrToFTest, BasicDecimalTests) {
runTest("1", 1, 0x3f800000);
runTest("123", 3, 0x42f60000);
runTest("1234567890", 10, 0x4e932c06u);
runTest("123456789012345678901", 21, 0x60d629d4);
runTest("0.1", 3, 0x3dcccccdu);
runTest(".1", 2, 0x3dcccccdu);
runTest("-0.123456789", 12, 0xbdfcd6eau);
runTest("0.11111111111111111111", 22, 0x3de38e39u);
runTest("0.0000000000000000000000001", 27, 0x15f79688u);
}

TEST_F(LlvmLibcStrToFTest, DecimalOutOfRangeTests) {
runTest("555E36", 6, 0x7f800000, ERANGE);
runTest("1e-10000", 8, 0x0, ERANGE);
}

TEST_F(LlvmLibcStrToFTest, DecimalsWithRoundingProblems) {
runTest("20040229", 8, 0x4b98e512);
runTest("20040401", 8, 0x4b98e568);
runTest("9E9", 3, 0x50061c46);
}

TEST_F(LlvmLibcStrToFTest, DecimalSubnormals) {
runTest("1.4012984643248170709237295832899161312802619418765e-45", 55, 0x1);
}

TEST_F(LlvmLibcStrToFTest, DecimalWithLongExponent) {
runTest("1e2147483648", 12, 0x7f800000, ERANGE);
runTest("1e2147483646", 12, 0x7f800000, ERANGE);
runTest("100e2147483646", 14, 0x7f800000, ERANGE);
runTest("1e-2147483647", 13, 0x0, ERANGE);
runTest("1e-2147483649", 13, 0x0, ERANGE);
}

TEST_F(LlvmLibcStrToFTest, BasicHexadecimalTests) {
runTest("0x1", 3, 0x3f800000);
runTest("0x10", 4, 0x41800000);
runTest("0x11", 4, 0x41880000);
runTest("0x0.1234", 8, 0x3d91a000);
}

TEST_F(LlvmLibcStrToFTest, HexadecimalSubnormalTests) {
runTest("0x0.0000000000000000000000000000000002", 38, 0x4000);

// This is the largest subnormal number as represented in hex
runTest("0x0.00000000000000000000000000000003fffff8", 42, 0x7fffff);
}

TEST_F(LlvmLibcStrToFTest, HexadecimalSubnormalRoundingTests) {
// This is the largest subnormal number that gets rounded down to 0 (as a
// float)
runTest("0x0.00000000000000000000000000000000000004", 42, 0x0, ERANGE);

// This is slightly larger, and thus rounded up
runTest("0x0.000000000000000000000000000000000000041", 43, 0x00000001,
ERANGE);

// These check that we're rounding to even properly
runTest("0x0.0000000000000000000000000000000000000b", 42, 0x00000001, ERANGE);
runTest("0x0.0000000000000000000000000000000000000c", 42, 0x00000002, ERANGE);
}

TEST_F(LlvmLibcStrToFTest, HexadecimalNormalRoundingTests) {
// This also checks the round to even behavior by checking three adjacent
// numbers.
// This gets rounded down to even
runTest("0x123456500", 11, 0x4f91a2b2);
// This doesn't get rounded at all
runTest("0x123456600", 11, 0x4f91a2b3);
// This gets rounded up to even
runTest("0x123456700", 11, 0x4f91a2b4);
}

TEST_F(LlvmLibcStrToFTest, HexadecimalOutOfRangeTests) {
runTest("0x123456789123456789123456789123456789", 38, 0x7f800000, ERANGE);
runTest("-0x123456789123456789123456789123456789", 39, 0xff800000, ERANGE);
runTest("0x0.00000000000000000000000000000000000001", 42, 0x0, ERANGE);
}

TEST_F(LlvmLibcStrToFTest, InfTests) {
runTest("INF", 3, 0x7f800000);
runTest("INFinity", 8, 0x7f800000);
runTest("infnity", 3, 0x7f800000);
runTest("infinit", 3, 0x7f800000);
runTest("infinfinit", 3, 0x7f800000);
runTest("innf", 0, 0x0);
runTest("-inf", 4, 0xff800000);
runTest("-iNfInItY", 9, 0xff800000);
}

TEST_F(LlvmLibcStrToFTest, NaNTests) {
runTest("NaN", 3, 0x7fc00000);
runTest("-nAn", 4, 0xffc00000);
runTest("NaN()", 5, 0x7fc00000);
runTest("NaN(1234)", 9, 0x7fc004d2);
runTest("NaN( 1234)", 3, 0x7fc00000);
}