toInt() bug, how unfortunate

So the documentation for toInt() says this:

"Converts a valid String to an integer."
"If no valid conversion could be performed... a zero is returned."

Unfortunately, it doesn't actually do that. toInt() is perfectly happy to perform illegal string->int conversions.

For example, I wrote a custom terminal that parses commands in the format "-command val1 val2". val1 and val2 are 32bit binary data, sent as decimal strings. I use toInt() to parse them into binary. However, if I send 0xFFFFFFFF, toInt() will convert it to 2147483647, which is 0x7FFFFFFF. It should have returned 0.

Is there a good function I can use instead, which will give the correct result?

Post your test code, please.
Also, which board did you test this on?

Hi,
I tested like this:

String a = "FFFFFFFF";

void setup() {
  Serial.begin(115200);
  delay(100);
}

void loop() {
  Serial.print("a results  ");
  Serial.println(a.toInt(), HEX);
  while (1) {}
}

ad the results:

a results  0

Post your code because "FFFFFFFF" does return 0 because toInt() does not parse Hex..

don't use the String class and use strtol() or if you require long long strtoll()

You can use the endptr to assess if the conversion went fine or failed and your hex number can start with 0x (you get to choose the base)

However, if I send 0xFFFFFFFF, toInt() will convert it to 2147483647

What are you talking about? Modify @ruilviana's sketch and prove this assertion.

You can toss it right into the wokwi simulator, which is where I learned more than I will ever need to about toInt().

Or share your own code where something happens for you that doesn't for us.

a7

toInt returns a 'long'. The reference: toInt() - Arduino Reference


It is not good to deal with 32-bits of a unsigned long.
The source code of toInt is here: ArduinoCore-API/String.cpp at master · arduino/ArduinoCore-API · GitHub
It is just a "atol(buffer)" of the buffer which is allocated in the heap.
I made a test sketch:

// For: https://forum.arduino.cc/t/toint-bug-how-unfortunate/1041669
//
// toInt reference:
//   https://www.arduino.cc/reference/en/language/variables/data-types/string/functions/toint/
//   It returns a 'long', that is a signed long, 32 bits on a Arduino Uno
//
// If the return value of toInt is converted from long to unsigned long,
// it returns the right bits. That is awkward.

void setup() 
{
  Serial.begin(115200);

  unsigned long a = 0xFFFFFFFF;
  Serial.print( "a = ");
  Serial.print( a);                 // print as decimal
  Serial.print( " = 0x");
  Serial.println( a, HEX);          // print as hexadecimal

  String s = String(a);             // use as decimal number
  Serial.println( "s = \"" + s + "\"");

  long b = s.toInt();               // convert to binary
  Serial.print( "b = ");
  Serial.println( b);

  unsigned long c = (unsigned long) s.toInt(); // use it as unsigned
  Serial.print( "c = ");
  Serial.println( c);
}

void loop() {}

That is awkward, when the decimal number is converted via a signed long of "atol()", and converted to unsigned long, then it is still the right number :grimacing:

The sketch with a Arduino Uno in Wokwi simulator: toInt.ino - Wokwi Arduino and ESP32 Simulator
It is possible to make changes and test it, but you need to log in to be able to store the sketch to your own Wokwi projects.

I tried running my code on Wokwi and it gives a different result (the correct result) compared to when I ran it on the actual hardware... I will try to figure this out tomorrow.

Thanks for the wonderful responses!

What was your actual hardware??

Please do. Any infidelity of the wokwi will be of great interest to Urish and his boys over there doing the heroic dev work which gives us wokwi.

As you can imagine, if this kind of deficiency exists, it makes the simulator almost useless less useful.

wokwi uses the Arduino tool chain to create the code which then runs on a full-up simulator of the chips that run the code on the various Arduino boards it covers. The several problems I ever knew about were traced to errors or omissions in the chip emulation part.

a7

My hardware is Teensy 4.1.

I wrote a simple test sketch and ran it on Wokwi and the actual Teensy, and got different results:

#include <stdint.h>

void setup() {
  Serial.begin(115200);
}

void loop() {
  String teststr = "4294967295";
  Serial.println(teststr);
  uint32_t temp = (uint32_t)(teststr.toInt());
  Serial.println(String(temp));
  delay(1000);
}

On Wokwi, this correctly prints:
4294967295
4294967295

On the Teensy hardware, this prints:
4294967295
2147483647

Alright, I found a conversion method that seems to work, using strtoul:

#include <stdint.h>
#include <stdlib.h>

void setup() {
  Serial.begin(115200);
}

void loop() {
  String teststr = "4294967295";
  Serial.print("\nOriginal value = ");
  Serial.println(teststr);
  
  uint32_t temp = (uint32_t)(teststr.toInt());
  Serial.print("Using toInt = ");
  Serial.println(String(temp));
  
  uint32_t temp2 = strtoul(teststr.c_str(), NULL, 10);
  Serial.print("Using strtoul = ");
  Serial.println(String(temp2));
  
  delay(2000);
}

I found this works on both Wokwi and the Teensy.

well that's a pain. My advice stands : don't use the String class and use strtol() or if you require long long strtoll()

if you want unsigned there is also strtoul()

When you had the flaw with toInt(), what board did you select in the wokwi? Since it does not emulate the Teensy, I hope at least you selected a board with int32_t integers as default.

If you did not, the behaviour is entirely normal/explainable. By someone. :expressionless:

a7

Fair question. The board in Wokwi was Uno.

However, in my code I always specify exact types (i.e. uint32_t) to avoid this issue. In embedded programming writing things like "int" is just begging for trouble after all...

I would also point out - it's weird that the bug is on Teensy, which is 32 bit. The Uno on Wokwi had the correct behavior.

So is using the String class on an Uno and the like.

Why?

Ah, I see you are unaware of the problems (no/poor garbage collection): Don't Use Strings

Because it can leave holes bigger than those in a swiss cheese in the memory of the microcontroller if incorrectly used.


Source: File:Emmental 015.jpg - Wikipedia

One explanation: The Evils of Arduino Strings | Majenko's Hardware Hacking Blog

Think about what the above is doing. toint() converts the String to an int, NOT a uint. Your input, "4294967295", is 2X the maximum value for a 32-bit int. So, it gets converted to the largest possible value, which is 2147483647. The fact that you then cast the result of uint32_t changes nothing. So, toint() is working exactly as it should, and the wokwi simulation is wrong.

If you want it to convert to a uint32_t, you have to write your own function to parse the String to uint32_t. toint will not do it.

the proof of the pudding is in the eating

void setup() {
  Serial.begin(115200);

  String v = "4294967295";
  int32_t signedData = v.toInt(); // really to a long
  uint32_t unsignedData = signedData;
  uint32_t unsignedDataC = strtoul(v.c_str(), nullptr, 10);

  Serial.println(signedData);
  Serial.println(unsignedData);
  Serial.println(unsignedDataC);
}

void loop() {}