[libc-commits] [libc] 9a32e53 - [libc] add scanf float converter

Michael Jones via libc-commits libc-commits at lists.llvm.org
Thu Jan 19 09:26:33 PST 2023


Author: Michael Jones
Date: 2023-01-19T09:26:25-08:00
New Revision: 9a32e53941ccb6ccd55d5ecd305ecd16b90bfd58

URL: https://github.com/llvm/llvm-project/commit/9a32e53941ccb6ccd55d5ecd305ecd16b90bfd58
DIFF: https://github.com/llvm/llvm-project/commit/9a32e53941ccb6ccd55d5ecd305ecd16b90bfd58.diff

LOG: [libc] add scanf float converter

This patch adds the %f/F/e/E/g/G/a/A conversions for scanf, as well as
accompanying tests. This implementation matches the definition set forth
in the standard, which may conflict with some other implementations.

Reviewed By: sivachandra

Differential Revision: https://reviews.llvm.org/D141091

Added: 
    libc/src/stdio/scanf_core/float_converter.cpp
    libc/src/stdio/scanf_core/float_converter.h

Modified: 
    libc/src/stdio/scanf_core/CMakeLists.txt
    libc/src/stdio/scanf_core/converter.cpp
    libc/src/stdio/scanf_core/core_structs.h
    libc/src/stdio/scanf_core/int_converter.cpp
    libc/test/src/stdio/CMakeLists.txt
    libc/test/src/stdio/sscanf_test.cpp

Removed: 
    


################################################################################
diff  --git a/libc/src/stdio/scanf_core/CMakeLists.txt b/libc/src/stdio/scanf_core/CMakeLists.txt
index ab0530d6d3861..54fa40d5363b4 100644
--- a/libc/src/stdio/scanf_core/CMakeLists.txt
+++ b/libc/src/stdio/scanf_core/CMakeLists.txt
@@ -79,9 +79,12 @@ add_object_library(
     converter.cpp
     string_converter.cpp
     int_converter.cpp
+    float_converter.cpp
   HDRS
     converter.h
     int_converter.h
+    string_converter.h
+    float_converter.h
   DEPENDS
     .reader
     .core_structs
@@ -89,6 +92,9 @@ add_object_library(
     libc.src.__support.CPP.bitset
     libc.src.__support.CPP.string_view
     libc.src.__support.CPP.limits
+    libc.src.__support.char_vector
+    libc.include.errno
+    libc.src.errno.errno
 )
 
 add_object_library(

diff  --git a/libc/src/stdio/scanf_core/converter.cpp b/libc/src/stdio/scanf_core/converter.cpp
index fbb2c1bcd5ee5..c5a2932fb18f8 100644
--- a/libc/src/stdio/scanf_core/converter.cpp
+++ b/libc/src/stdio/scanf_core/converter.cpp
@@ -12,6 +12,9 @@
 #include "src/stdio/scanf_core/core_structs.h"
 #include "src/stdio/scanf_core/reader.h"
 
+#ifndef LLVM_LIBC_SCANF_DISABLE_FLOAT
+#include "src/stdio/scanf_core/float_converter.h"
+#endif // LLVM_LIBC_SCANF_DISABLE_FLOAT
 #include "src/stdio/scanf_core/int_converter.h"
 #include "src/stdio/scanf_core/string_converter.h"
 
@@ -43,24 +46,22 @@ int convert(Reader *reader, const FormatSection &to_conv) {
     if (ret_val != READ_OK)
       return ret_val;
     return convert_int(reader, to_conv);
-    // #ifndef LLVM_LIBC_SCANF_DISABLE_FLOAT
-    //   case 'f':
-    //   case 'F':
-    //   case 'e':
-    //   case 'E':
-    //   case 'a':
-    //   case 'A':
-    //   case 'g':
-    //   case 'G':
-    //     ret_val = raw_match(reader, " ");
-    //     if (ret_val != READ_OK)
-    //       return ret_val;
-    //     return convert_float(reader, to_conv);
-    // #endif // LLVM_LIBC_SCANF_DISABLE_FLOAT
-    // #ifndef LLVM_LIBC_SCANF_DISABLE_WRITE_INT
+#ifndef LLVM_LIBC_SCANF_DISABLE_FLOAT
+  case 'f':
+  case 'F':
+  case 'e':
+  case 'E':
+  case 'a':
+  case 'A':
+  case 'g':
+  case 'G':
+    ret_val = raw_match(reader, " ");
+    if (ret_val != READ_OK)
+      return ret_val;
+    return convert_float(reader, to_conv);
+#endif // LLVM_LIBC_SCANF_DISABLE_FLOAT
     //   case 'n':
     //     return convert_write_int(reader, to_conv);
-    // #endif // LLVM_LIBC_SCANF_DISABLE_WRITE_INT
     //   case 'p':
     //     ret_val = raw_match(reader, " ");
     //     if (ret_val != READ_OK)

diff  --git a/libc/src/stdio/scanf_core/core_structs.h b/libc/src/stdio/scanf_core/core_structs.h
index 7f331db362023..4555595ab3bff 100644
--- a/libc/src/stdio/scanf_core/core_structs.h
+++ b/libc/src/stdio/scanf_core/core_structs.h
@@ -84,6 +84,7 @@ enum ErrorCodes : int {
   FILE_READ_ERROR = -1,
   FILE_STATUS_ERROR = -2,
   MATCHING_FAILURE = -3,
+  ALLOCATION_FAILURE = -4,
 };
 } // namespace scanf_core
 } // namespace __llvm_libc

diff  --git a/libc/src/stdio/scanf_core/float_converter.cpp b/libc/src/stdio/scanf_core/float_converter.cpp
new file mode 100644
index 0000000000000..1a0ce42863f35
--- /dev/null
+++ b/libc/src/stdio/scanf_core/float_converter.cpp
@@ -0,0 +1,255 @@
+//===-- Int type specifier converters for scanf -----------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/stdio/scanf_core/float_converter.h"
+
+#include "src/__support/CPP/limits.h"
+#include "src/__support/char_vector.h"
+#include "src/__support/ctype_utils.h"
+#include "src/__support/str_to_float.h"
+#include "src/stdio/scanf_core/core_structs.h"
+#include "src/stdio/scanf_core/reader.h"
+
+#include <stddef.h>
+
+namespace __llvm_libc {
+namespace scanf_core {
+
+constexpr char inline to_lower(char a) { return a | 32; }
+
+void write_with_length(char *str, const FormatSection &to_conv) {
+  if ((to_conv.flags & NO_WRITE) != 0) {
+    return;
+  }
+
+  void *output_ptr = to_conv.output_ptr;
+
+  LengthModifier lm = to_conv.length_modifier;
+  switch (lm) {
+  case (LengthModifier::l): {
+    auto value = internal::strtofloatingpoint<double>(str, nullptr);
+    *reinterpret_cast<double *>(output_ptr) = value;
+    break;
+  }
+  case (LengthModifier::L): {
+    auto value = internal::strtofloatingpoint<long double>(str, nullptr);
+    *reinterpret_cast<long double *>(output_ptr) = value;
+    break;
+  }
+  default: {
+    auto value = internal::strtofloatingpoint<float>(str, nullptr);
+    *reinterpret_cast<float *>(output_ptr) = value;
+    break;
+  }
+  }
+}
+
+// All of the floating point conversions are the same for scanf, every name will
+// accept every style.
+int convert_float(Reader *reader, const FormatSection &to_conv) {
+  // %a/A/e/E/f/F/g/G "Matches an optionally signed floating-point number,
+  // infinity, or NaN, whose format is the same as expected for the subject
+  // sequence of the strtod function. The corresponding argument shall be a
+  // pointer to floating."
+
+  CharVector out_str = CharVector();
+  bool is_number = false;
+
+  size_t max_width = cpp::numeric_limits<size_t>::max();
+  if (to_conv.max_width > 0) {
+    max_width = to_conv.max_width;
+  }
+
+  char cur_char = reader->getc();
+  // Handle the sign.
+  if (cur_char == '+' || cur_char == '-') {
+    if (!out_str.append(cur_char)) {
+      return ALLOCATION_FAILURE;
+    }
+    if (out_str.length() == max_width) {
+      return MATCHING_FAILURE;
+    } else {
+      cur_char = reader->getc();
+    }
+  }
+
+  static constexpr char DECIMAL_POINT = '.';
+  static const char inf_string[] = "infinity";
+
+  // Handle inf
+
+  if (to_lower(cur_char) == inf_string[0]) {
+    size_t inf_index = 0;
+
+    for (; to_lower(cur_char) == inf_string[inf_index] &&
+           inf_index < sizeof(inf_string) && out_str.length() < max_width;
+         ++inf_index) {
+      if (!out_str.append(cur_char)) {
+        return ALLOCATION_FAILURE;
+      }
+      cur_char = reader->getc();
+    }
+
+    if (inf_index == 3 || inf_index == sizeof(inf_string) - 1) {
+      write_with_length(out_str.c_str(), to_conv);
+      return READ_OK;
+    } else {
+      return MATCHING_FAILURE;
+    }
+  }
+
+  static const char nan_string[] = "nan";
+
+  // Handle nan
+  if (to_lower(cur_char) == nan_string[0]) {
+    size_t nan_index = 0;
+
+    for (; to_lower(cur_char) == nan_string[nan_index] &&
+           nan_index < sizeof(nan_string) && out_str.length() < max_width;
+         ++nan_index) {
+      if (!out_str.append(cur_char)) {
+        return ALLOCATION_FAILURE;
+      }
+      cur_char = reader->getc();
+    }
+
+    if (nan_index == sizeof(nan_string) - 1) {
+      write_with_length(out_str.c_str(), to_conv);
+      return READ_OK;
+    } else {
+      return MATCHING_FAILURE;
+    }
+  }
+
+  // Assume base of 10 by default but check if it is actually base 16.
+  int base = 10;
+
+  // If the string starts with 0 it might be in hex.
+  if (cur_char == '0') {
+    is_number = true;
+    // Read the next character to check.
+    if (!out_str.append(cur_char)) {
+      return ALLOCATION_FAILURE;
+    }
+    // If we've hit the end, then this is "0", which is valid.
+    if (out_str.length() == max_width) {
+      write_with_length(out_str.c_str(), to_conv);
+      return READ_OK;
+    } else {
+      cur_char = reader->getc();
+    }
+
+    // If that next character is an 'x' then this is a hexadecimal number.
+    if (to_lower(cur_char) == 'x') {
+      base = 16;
+
+      if (!out_str.append(cur_char)) {
+        return ALLOCATION_FAILURE;
+      }
+      // If we've hit the end here, we have "0x" which is a valid prefix to a
+      // floating point number, and will be evaluated to 0.
+      if (out_str.length() == max_width) {
+        write_with_length(out_str.c_str(), to_conv);
+        return READ_OK;
+      } else {
+        cur_char = reader->getc();
+      }
+    }
+  }
+
+  const char exponent_mark = ((base == 10) ? 'e' : 'p');
+  bool after_decimal = false;
+
+  // The format for the remaining characters at this point is DD.DDe+/-DD for
+  // base 10 and XX.XXp+/-DD for base 16
+
+  // This handles the digits before and after the decimal point, but not the
+  // exponent.
+  while (out_str.length() < max_width) {
+    if (internal::isalnum(cur_char) &&
+        internal::b36_char_to_int(cur_char) < base) {
+      is_number = true;
+      if (!out_str.append(cur_char)) {
+        return ALLOCATION_FAILURE;
+      }
+      cur_char = reader->getc();
+    } else if (cur_char == DECIMAL_POINT && !after_decimal) {
+      after_decimal = true;
+      if (!out_str.append(cur_char)) {
+        return ALLOCATION_FAILURE;
+      }
+      cur_char = reader->getc();
+    } else {
+      break;
+    }
+  }
+
+  // Handle the exponent, which has an exponent mark, an optional sign, and
+  // decimal digits.
+  if (to_lower(cur_char) == exponent_mark) {
+    if (!out_str.append(cur_char)) {
+      return ALLOCATION_FAILURE;
+    }
+    if (out_str.length() == max_width) {
+      // This is laid out in the standard as being a matching error (100e is not
+      // a valid float) but may conflict with existing implementations.
+      return MATCHING_FAILURE;
+    } else {
+      cur_char = reader->getc();
+    }
+
+    if (cur_char == '+' || cur_char == '-') {
+      if (!out_str.append(cur_char)) {
+        return ALLOCATION_FAILURE;
+      }
+      if (out_str.length() == max_width) {
+        return MATCHING_FAILURE;
+      } else {
+        cur_char = reader->getc();
+      }
+    }
+
+    // It is specified by the standard that "100er" is a matching failure since
+    // the longest prefix of a possibly valid floating-point number (which is
+    // "100e") is not a valid floating-point number. If there is an exponent
+    // mark then there must be a digit after it else the number is not valid.
+    // Some implementations will roll back two characters (to just "100") and
+    // accept that since the prefix is not valid, and some will interpret an
+    // exponent mark followed by no digits as an additional exponent of 0
+    // (accepting "100e" and returning 100.0). Both of these behaviors are wrong
+    // by the standard, but they may be used in real code, see Hyrum's law. This
+    // code follows the standard, but may be incompatible due to code expecting
+    // these bugs.
+    if (!internal::isdigit(cur_char)) {
+      return MATCHING_FAILURE;
+    }
+
+    while (internal::isdigit(cur_char) && out_str.length() < max_width) {
+      if (!out_str.append(cur_char)) {
+        return ALLOCATION_FAILURE;
+      }
+      cur_char = reader->getc();
+    }
+  }
+
+  // We always read one more character than will be used, so we have to put the
+  // last one back.
+  reader->ungetc(cur_char);
+
+  // If we haven't actually found any digits, this is a matching failure (this
+  // catches cases like "+.")
+  if (!is_number) {
+    return MATCHING_FAILURE;
+  }
+  write_with_length(out_str.c_str(), to_conv);
+
+  return READ_OK;
+}
+
+} // namespace scanf_core
+} // namespace __llvm_libc

diff  --git a/libc/src/stdio/scanf_core/float_converter.h b/libc/src/stdio/scanf_core/float_converter.h
new file mode 100644
index 0000000000000..e8abe8d20bcea
--- /dev/null
+++ b/libc/src/stdio/scanf_core/float_converter.h
@@ -0,0 +1,25 @@
+//===-- Float type specifier converter for scanf ----------------*- 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_STDIO_SCANF_CORE_FLOAT_CONVERTER_H
+#define LLVM_LIBC_SRC_STDIO_SCANF_CORE_FLOAT_CONVERTER_H
+
+#include "src/stdio/scanf_core/core_structs.h"
+#include "src/stdio/scanf_core/reader.h"
+
+#include <stddef.h>
+
+namespace __llvm_libc {
+namespace scanf_core {
+
+int convert_float(Reader *reader, const FormatSection &to_conv);
+
+} // namespace scanf_core
+} // namespace __llvm_libc
+
+#endif // LLVM_LIBC_SRC_STDIO_SCANF_CORE_FLOAT_CONVERTER_H

diff  --git a/libc/src/stdio/scanf_core/int_converter.cpp b/libc/src/stdio/scanf_core/int_converter.cpp
index 1087166742b6c..be88a01f942d7 100644
--- a/libc/src/stdio/scanf_core/int_converter.cpp
+++ b/libc/src/stdio/scanf_core/int_converter.cpp
@@ -6,7 +6,7 @@
 //
 //===----------------------------------------------------------------------===//
 
-#include "src/stdio/scanf_core/string_converter.h"
+#include "src/stdio/scanf_core/int_converter.h"
 
 #include "src/__support/CPP/limits.h"
 #include "src/__support/ctype_utils.h"

diff  --git a/libc/test/src/stdio/CMakeLists.txt b/libc/test/src/stdio/CMakeLists.txt
index f74fa46a5e77d..e94432d069f2e 100644
--- a/libc/test/src/stdio/CMakeLists.txt
+++ b/libc/test/src/stdio/CMakeLists.txt
@@ -182,6 +182,8 @@ add_libc_unittest(
     sscanf_test.cpp
   DEPENDS
     libc.src.stdio.sscanf
+  LINK_LIBRARIES
+    LibcFPTestHelpers
 )
 
 add_libc_unittest(

diff  --git a/libc/test/src/stdio/sscanf_test.cpp b/libc/test/src/stdio/sscanf_test.cpp
index b3e146f1d64be..fc67593f57f0b 100644
--- a/libc/test/src/stdio/sscanf_test.cpp
+++ b/libc/test/src/stdio/sscanf_test.cpp
@@ -7,10 +7,14 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/__support/CPP/limits.h"
+#include "src/__support/FPUtil/FPBits.h"
+#include "src/__support/FPUtil/PlatformDefs.h"
+
 #include "src/stdio/sscanf.h"
 
 #include <stdio.h> // For EOF
 
+#include "utils/UnitTest/FPMatcher.h"
 #include "utils/UnitTest/Test.h"
 
 TEST(LlvmLibcSScanfTest, SimpleStringConv) {
@@ -209,6 +213,365 @@ TEST(LlvmLibcSScanfTest, IntConvNoWriteTests) {
   EXPECT_EQ(result, 0);
 }
 
+TEST(LlvmLibcSScanfTest, FloatConvSimple) {
+  int ret_val;
+  float result = 0;
+
+  float inf = __llvm_libc::fputil::FPBits<float>::inf().get_val();
+  float nan = __llvm_libc::fputil::FPBits<float>::build_nan(1);
+
+  ret_val = __llvm_libc::sscanf("123", "%f", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, 123.0);
+
+  ret_val = __llvm_libc::sscanf("456.1", "%a", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, 456.1);
+
+  ret_val = __llvm_libc::sscanf("0x789.ap0", "%e", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, 0x789.ap0);
+
+  ret_val = __llvm_libc::sscanf("0x.8", "%e", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, 0x0.8p0);
+
+  ret_val = __llvm_libc::sscanf("0x8.", "%e", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, 0x8.0p0);
+
+  ret_val = __llvm_libc::sscanf("+12.0e1", "%g", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, 12.0e1);
+
+  ret_val = __llvm_libc::sscanf("inf", "%F", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, inf);
+
+  ret_val = __llvm_libc::sscanf("NaN", "%A", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, nan);
+
+  ret_val = __llvm_libc::sscanf("-InFiNiTy", "%E", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, -inf);
+
+  ret_val = __llvm_libc::sscanf("1e10", "%G", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, 1e10);
+
+  ret_val = __llvm_libc::sscanf(".1", "%G", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, 0.1);
+
+  ret_val = __llvm_libc::sscanf("1.", "%G", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, 1.0);
+
+  ret_val = __llvm_libc::sscanf("0", "%f", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, 0.0);
+
+  ret_val = __llvm_libc::sscanf("Not a float", "%f", &result);
+  EXPECT_EQ(ret_val, 0);
+}
+
+TEST(LlvmLibcSScanfTest, FloatConvLengthModifier) {
+  int ret_val;
+  double d_result = 0;
+  long double ld_result = 0;
+
+  double d_inf = __llvm_libc::fputil::FPBits<double>::inf().get_val();
+  long double ld_nan = __llvm_libc::fputil::FPBits<long double>::build_nan(1);
+
+  ret_val = __llvm_libc::sscanf("123", "%lf", &d_result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(d_result, 123.0);
+
+  ret_val = __llvm_libc::sscanf("456.1", "%La", &ld_result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(ld_result, 456.1L);
+
+  ret_val = __llvm_libc::sscanf("inf", "%le", &d_result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(d_result, d_inf);
+
+  ret_val = __llvm_libc::sscanf("nan", "%Lg", &ld_result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(ld_result, ld_nan);
+
+  ret_val = __llvm_libc::sscanf("1e-300", "%lF", &d_result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(d_result, 1e-300);
+
+  ret_val = __llvm_libc::sscanf("1.0e600", "%LA", &ld_result);
+  EXPECT_EQ(ret_val, 1);
+// 1e600 may be larger than the maximum long double (if long double is double).
+// In that case both of these should be evaluated as inf.
+#ifdef LONG_DOUBLE_IS_DOUBLE
+  EXPECT_FP_EQ(ld_result, d_inf);
+#else
+  EXPECT_FP_EQ(ld_result, 1.0e600L);
+#endif
+}
+
+TEST(LlvmLibcSScanfTest, FloatConvLongNumber) {
+  int ret_val;
+  float result = 0;
+  double d_result = 0;
+
+  // 32 characters
+  ret_val =
+      __llvm_libc::sscanf("123456789012345678901234567890.0", "%f", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, 123456789012345678901234567890.0f);
+
+  // 64 characters
+  ret_val = __llvm_libc::sscanf(
+      "123456789012345678901234567890123456789012345678901234567890.000", "%la",
+      &d_result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(
+      d_result,
+      123456789012345678901234567890123456789012345678901234567890.000);
+
+  // 128 characters
+  ret_val = __llvm_libc::sscanf(
+      "123456789012345678901234567890123456789012345678901234567890"
+      "123456789012345678901234567890123456789012345678901234567890.0000000",
+      "%le", &d_result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(
+      d_result,
+      123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890.0000000);
+
+  // 256 characters
+  ret_val = __llvm_libc::sscanf("10000000000000000000000000000000"
+                                "00000000000000000000000000000000"
+                                "00000000000000000000000000000000"
+                                "00000000000000000000000000000000"
+                                "00000000000000000000000000000000"
+                                "00000000000000000000000000000000"
+                                "00000000000000000000000000000000"
+                                "00000000000000000000000000000000",
+                                "%lf", &d_result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(d_result, 1e255);
+
+  // 288 characters
+  ret_val = __llvm_libc::sscanf("10000000000000000000000000000000"
+                                "00000000000000000000000000000000"
+                                "00000000000000000000000000000000"
+                                "00000000000000000000000000000000"
+                                "00000000000000000000000000000000"
+                                "00000000000000000000000000000000"
+                                "00000000000000000000000000000000"
+                                "00000000000000000000000000000000"
+                                "00000000000000000000000000000000",
+                                "%lf", &d_result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(d_result, 1e287);
+}
+
+TEST(LlvmLibcSScanfTest, FloatConvComplexParsing) {
+  int ret_val;
+  float result = 0;
+
+  float inf = __llvm_libc::fputil::FPBits<float>::inf().get_val();
+  float nan = __llvm_libc::fputil::FPBits<float>::build_nan(1);
+
+  ret_val = __llvm_libc::sscanf("0x1.0e3", "%f", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, 0x1.0e3p0);
+
+  ret_val = __llvm_libc::sscanf("", "%a", &result);
+  EXPECT_EQ(ret_val, 0);
+
+  ret_val = __llvm_libc::sscanf("+", "%a", &result);
+  EXPECT_EQ(ret_val, 0);
+
+  ret_val = __llvm_libc::sscanf("-", "%a", &result);
+  EXPECT_EQ(ret_val, 0);
+
+  ret_val = __llvm_libc::sscanf("+.", "%a", &result);
+  EXPECT_EQ(ret_val, 0);
+
+  ret_val = __llvm_libc::sscanf("-.e+10", "%a", &result);
+  EXPECT_EQ(ret_val, 0);
+
+  // This is a specific example from the standard. Its behavior diverges from
+  // other implementations that accept "100e" as being the same as "100e0"
+  ret_val = __llvm_libc::sscanf("100er", "%a", &result);
+  EXPECT_EQ(ret_val, 0);
+
+  ret_val = __llvm_libc::sscanf("nah", "%a", &result);
+  EXPECT_EQ(ret_val, 0);
+
+  ret_val = __llvm_libc::sscanf("indirection", "%a", &result);
+  EXPECT_EQ(ret_val, 0);
+
+  ret_val = __llvm_libc::sscanf("infnan", "%a", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, inf);
+
+  ret_val = __llvm_libc::sscanf("naninf", "%a", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, nan);
+
+  ret_val = __llvm_libc::sscanf("infinityinfinity", "%a", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, inf);
+
+  // For %f to accept a string as representing it has to be either "inf" or
+  // "infinity" when it stops. It only stops when it encounters a character that
+  // isn't the next one in the string, so it accepts "infi" as the the longest
+  // prefix of a possibly valid floating-point number, but determines that it is
+  // not valid and returns a matching failure. This is because it can only unget
+  // one character so when it finds that the character after the second 'i' is
+  // not the next character in "infinity" it can't rewind to the point where it
+  // had just "inf".
+  ret_val = __llvm_libc::sscanf("infi", "%a", &result);
+  EXPECT_EQ(ret_val, 0);
+
+  ret_val = __llvm_libc::sscanf("infinite", "%a", &result);
+  EXPECT_EQ(ret_val, 0);
+
+  ret_val = __llvm_libc::sscanf("-.1e1", "%f", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, -.1e1);
+
+  ret_val = __llvm_libc::sscanf("1.2.e1", "%f", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, 1.2);
+}
+
+/*
+TODO:
+  Max width tests
+*/
+
+TEST(LlvmLibcSScanfTest, FloatConvMaxWidth) {
+  int ret_val;
+  float result = 0;
+
+  float inf = __llvm_libc::fputil::FPBits<float>::inf().get_val();
+
+  ret_val = __llvm_libc::sscanf("123", "%3f", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, 123.0);
+
+  ret_val = __llvm_libc::sscanf("123", "%5f", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, 123.0);
+
+  ret_val = __llvm_libc::sscanf("456", "%1f", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, 4.0);
+
+  ret_val = __llvm_libc::sscanf("-789", "%1f", &result);
+  EXPECT_EQ(ret_val, 0);
+
+  ret_val = __llvm_libc::sscanf("-123", "%2f", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, -1.0);
+
+  ret_val = __llvm_libc::sscanf("inf", "%2f", &result);
+  EXPECT_EQ(ret_val, 0);
+
+  ret_val = __llvm_libc::sscanf("nan", "%1f", &result);
+  EXPECT_EQ(ret_val, 0);
+
+  ret_val = __llvm_libc::sscanf("-inf", "%3f", &result);
+  EXPECT_EQ(ret_val, 0);
+
+  ret_val = __llvm_libc::sscanf("-nan", "%3f", &result);
+  EXPECT_EQ(ret_val, 0);
+
+  // If the max length were not here this would fail as discussed above, but
+  // since the max length limits it to the 3 it succeeds.
+  ret_val = __llvm_libc::sscanf("infinite", "%3f", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, inf);
+
+  ret_val = __llvm_libc::sscanf("-infinite", "%4f", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, -inf);
+
+  ret_val = __llvm_libc::sscanf("01", "%1f", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, 0.0);
+
+  ret_val = __llvm_libc::sscanf("0x1", "%2f", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, 0.0);
+
+  ret_val = __llvm_libc::sscanf("100e", "%4f", &result);
+  EXPECT_EQ(ret_val, 0);
+
+  ret_val = __llvm_libc::sscanf("100e+10", "%5f", &result);
+  EXPECT_EQ(ret_val, 0);
+
+  ret_val = __llvm_libc::sscanf("100e10", "%5f", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, 100e1);
+}
+
+TEST(LlvmLibcSScanfTest, FloatConvNoWrite) {
+  int ret_val;
+  float result = 0;
+
+  ret_val = __llvm_libc::sscanf("123", "%*f", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, 0.0);
+
+  ret_val = __llvm_libc::sscanf("456.1", "%*a", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, 0.0);
+
+  ret_val = __llvm_libc::sscanf("0x789.ap0", "%*e", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, 0.0);
+
+  ret_val = __llvm_libc::sscanf("+12.0e1", "%*g", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, 0.0);
+
+  ret_val = __llvm_libc::sscanf("inf", "%*F", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, 0.0);
+
+  ret_val = __llvm_libc::sscanf("NaN", "%*A", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, 0.0);
+
+  ret_val = __llvm_libc::sscanf("-InFiNiTy", "%*E", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, 0.0);
+
+  ret_val = __llvm_libc::sscanf("1e10", "%*G", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, 0.0);
+
+  ret_val = __llvm_libc::sscanf(".1", "%*G", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, 0.0);
+
+  ret_val = __llvm_libc::sscanf("123", "%*3f", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, 0.0);
+
+  ret_val = __llvm_libc::sscanf("123", "%*5f", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, 0.0);
+
+  ret_val = __llvm_libc::sscanf("456", "%*1f", &result);
+  EXPECT_EQ(ret_val, 1);
+  EXPECT_FP_EQ(result, 0.0);
+
+  ret_val = __llvm_libc::sscanf("Not a float", "%*f", &result);
+  EXPECT_EQ(ret_val, 0);
+}
+
 TEST(LlvmLibcSScanfTest, CombinedConv) {
   int ret_val;
   int result = 0;


        


More information about the libc-commits mailing list