I am writing a program for the RP2040 microcontroller and cannot use the std
library. I have a function that takes in a &str
and prints it on a display. How can I print an integer (i32) without using the std
library?
All integer types implement core::fmt::Display
, which seems like it would work for your use case.
defmt
takes a different approach to printing values, delegating to the remote host running the terminal. It's more complicated to setup but removes all of the formatting machinery from the embedded binary, which can save some flash space (if that's important) and improves runtime performance for printing complex types.
It works by compressing the data going over the wire to a string identifier (which the terminal app resolves to the static format string) and a list of serialized (but unformatted) arguments.
It is a clever idea, but you might have enough CPU and memory bandwidth on RP2040 that you can do all of the printing on the device and send the entire string over serial IO. It worked out alright on one of my projects.
The other answers are all correct, but the actual logic here is pretty easy to do too:
#![no_std]
#[test]
fn test() {
assert_eq!(itoa(0).as_str(), "0");
assert_eq!(itoa(1).as_str(), "1");
assert_eq!(itoa(-1).as_str(), "-1");
assert_eq!(itoa(-9).as_str(), "-9");
assert_eq!(itoa(-10).as_str(), "-10");
assert_eq!(itoa(1234567).as_str(), "1234567");
assert_eq!(itoa(i32::MAX).as_str(), "2147483647");
assert_eq!(itoa(i32::MIN + 1).as_str(), "-2147483647");
assert_eq!(itoa(i32::MIN).as_str(), "-2147483648");
}
// Can't return str without a ref to a lifetime somewhere else,
// so instead return a buffer of the maximum i32 length
pub struct ItoaResult {
// i32 represents values from -2147483648 to 2147483647
buf: [u8; 11],
len: usize,
}
impl ItoaResult {
pub fn new() -> Self {
Self { buf: [0u8; 11], len: 0 }
}
fn push(&mut self, c: u8) {
self.len += 1;
self.buf[11usize.checked_sub(self.len).unwrap()] = c;
}
pub fn as_str(&self) -> &str {
core::str::from_utf8(&self.buf[11usize.checked_sub(self.len).unwrap()..]).unwrap()
}
}
pub fn itoa(mut value: i32) -> ItoaResult {
let mut result = ItoaResult::new();
let neg = value < 0;
loop {
result.push(b'0' + u8::try_from((value % 10).abs()).unwrap());
value /= 10;
// check at end of loop, so 0 prints to "0"
if value == 0 {
break;
}
}
if neg {
result.push(b'-');
}
result
}
The main trouble is handling getting positive digits from i32::MIN
(which you can't negate), which it turns out is simplest to handle in the laziest way, simply do the default %
implementation and .abs()
the result!
There's probably a nicer way to do the push()
, but really it's just a paranoid self.buf[11 - self.len] = c;
, which isn't that scary looking.
you can also just use i32::unsigned_abs
which never overflows.
Damn, I actually looked specifically for that and must have missed it next to the unchecked_*
methods. I checked the x86_64 assembly generated by both (with all the unchecked
methods used to get the cleanest output), and it indeed looks quite a bit nicer, adding 3 instructions before the loop and removing 6 inside the loop (going from 26 -> 20 instructions).
This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.