[libc-commits] [PATCH] D156981: [libc] Better IntegerToString API

Guillaume Chatelet via Phabricator via libc-commits libc-commits at lists.llvm.org
Tue Aug 8 04:55:26 PDT 2023


gchatelet marked 7 inline comments as done.
gchatelet added inline comments.


================
Comment at: libc/src/__support/StringUtil/signal_to_string.cpp:32
   // the buffer should be able to hold "Real-time signal" + ' ' + num_str
-  return (base_str_len + 1 + max_num_len) * sizeof(char);
+  return (base_str_len + 1 + int_buffer_size()) * sizeof(char);
 }
----------------
jhuber6 wrote:
> Do we need a helper for what is already a one-liner? I think it should fit without needing to be split to the next line.
I agree it's not useful anymore.


================
Comment at: libc/src/__support/integer_to_string.h:90
+  // Invariants
+  static_assert(BASE == 2 || BASE == 8 || BASE == 10 || BASE == 16,
+                "Invalid radix");
----------------
michaelrj wrote:
> why limit this to only these bases? Without the asserts this class would support any base from 2-36. Additionally, that would mirror how the string to integer functions work.
Although the API would absolutely work with other bases I couldn't come up with legitimate uses so I decided to restrict them.
I will revert to the previous state and provide a way to access other bases.


================
Comment at: libc/src/__support/integer_to_string.h:197
+    constexpr size_t digit_size = max(max_digits(), Fmt::MIN_DIGITS);
+    constexpr size_t sign_size = Fmt::BASE == 10 ? 1 : 0;
+    constexpr size_t prefix_size = Fmt::PREFIX ? 2 : 0;
----------------
michaelrj wrote:
> why are signs only supported in base 10?
This is consistent with :
 - `printf/scanf` specification
 - hex/binary calculator

It is technically doable but I don't see a use case for it. We can always lift this restriction afterwards if it proves useful.


================
Comment at: libc/src/__support/integer_to_string.h:229
+        is_negative = value < 0;
+        write_number(is_negative ? -value : value, sink);
+      } else {
----------------
michaelrj wrote:
> is there handling for `value` being the lowest negative value for a signed type (e.g. `LONG_MIN`)? In that case `-value` is equivalent to `value`.
> is there handling for `value` being the lowest negative value for a signed type (e.g. `LONG_MIN`)? In that case `-value` is equivalent to `value`.

Yes, this is covered by the tests.
```
  EXPECT(type, INT8_MIN, "-128");
  EXPECT(type, INT16_MIN, "-32768");
  EXPECT(type, INT32_MIN, "-2147483648");
  EXPECT(type, INT64_MIN, "-9223372036854775808");
```

C++17 does not enforce a single representation for signed integers but C++20 will enforce two's complement as the only valid representation of signed integer : "[However, all C++ compilers use two's complement representation, and as of C++20, it is the only representation allowed by the standard](https://en.cppreference.com/w/cpp/language/types#:~:text=However%2C%20all%20C,8%2Dbit%20type)".

Also "[The builtin unary minus operator calculates the negative of its promoted operand](https://en.cppreference.com/w/cpp/language/operator_arithmetic#:~:text=The%20builtin%20unary,bits%20after%20promotion.)" 

In the case of `int8_t`, `-INT8_MIN` will actually perform `-static_cast<int>(INT8_MIN)` which will end up being `int(128)` then this value is converted back to `uint8_t`.
Then the standard says "if the target type can represent the value, the value is unchanged" this is the case here, `128` is legal in `uint8_t` so this will be the value passed down to `write_number`.

The same reasoning works for `int16_t`, `int32_t` and `int64_t` except that type promotion does not always kick in. In that case I believe that [the second rule](https://en.cppreference.com/w/c/language/conversion#:~:text=otherwise%2C%20if%20the,implement%20modulo%20arithmetic.) applies. It basically states that modulo two arithmetic applies for the conversion from signed to unsigned. Practically speaking for `int64_t` this is equivalent to `std::bit_cast<uint64_t>(INT64_MIN)` leaving the bit pattern unchanged.

`INT64_MIN` interpreted as `int64_t`
```
hex : 8000 0000 0000 0000
dec : -9 223 372 036 854 775 808
```

interpreted as `uint64_t`
```
hex : 8000 0000 0000 0000
dec : 9 223 372 036 854 775 808
```


================
Comment at: libc/src/__support/integer_to_string.h:193-195
+    constexpr auto max = [](size_t a, size_t b) -> size_t {
+      return a > b ? a : b;
+    };
----------------
jhuber6 wrote:
> Unrelated, but this really should probably go in some common `libc` header since there's another use in `rpc_client.h`.
> Unrelated, but this really should probably go in some common `libc` header since there's another use in `rpc_client.h`.

We definitely need it. I plan to provide it in `libc/src/__support/CPP/algorithms.h`.


================
Comment at: libc/src/__support/integer_to_string.h:94
+
+using BinString = details::Config<2>;
+using OctString = details::Config<8>;
----------------
jhuber6 wrote:
> gchatelet wrote:
> > jhuber6 wrote:
> > > I'm not a fan of the name, since this isn't a string it's a config for the radix. Could we do something like `radix::Decimal`, etc?
> > The intent was that it could be read //naturally// : `IntegerTo<DecString::WithPrefix>` -> `Integer to dec string with prefix`.
> > Now I'm fine with another approach.
> > 
> > I'd like to gather a few other opinions / suggestions before changing though.
> I just feel like it's clearer if you have a function called `IntegerToString` which takes a radix config by template, e.g. `IntegerToString<radix::Decimal>`. It's more words but I just find it a little weird to 
> I just feel like it's clearer if you have a function called `IntegerToString` which takes a radix config by template, e.g. `IntegerToString<radix::Decimal>`. It's more words but I just find it a little weird to 

`IntegerToString<radix::XXX>` it is then.



Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D156981/new/

https://reviews.llvm.org/D156981



More information about the libc-commits mailing list