1

I am trying to do something similar to printf("My age is: %d\n", age); but I do not have access to the standard libraries. Instead, the print function I do have access to, print(), takes only a const char*.

I am not allowed to use sprintf or itoa or anything of that sort because that is simply not available. Is there a way I can print the number?

P.S. If you need more context, https://github.com/SpinalHDL/VexRiscv/issues/215 it is :)

Thanks in advance

5
  • do you have a Unix OS on that machine? Because the impression I get this is bare metal. Commented Oct 23, 2021 at 15:17
  • 2
    This isn't really a unix/linux question, more how to write itoa. Commented Oct 24, 2021 at 9:40
  • This is usually done using recursion, but none of the answers do that. Commented Oct 30, 2021 at 13:04
  • @ThomasDickey Added a recursive solution to my answer. Commented Oct 31, 2021 at 12:00
  • 1
    fwiw, when I wrote this, I did a recursive function to convert Roman numerals as well. Commented Oct 31, 2021 at 13:01

3 Answers 3

5

It is kind of clumsy. You can get digits starting from the low end, using % 10 (modulus) to isolate the units digit and / 10 (integer divide) to shift the number to the right. You do that in a loop until the int goes down to zero. If the whole thing is zero, you have to set the '0' char yourself, because the loop will not then do the first iteration.

You need to add '0' to each digit to make it an ASCII digit, and you need to store the successive digits in a char array.

You need to append a NUL ('\0') char on the end as a string terminator.

Then you need to reverse the whole string, because the digits came out in reverse order. Alternatively, you can fill the char array from the end, but then you have to copy the whole string (including the NUL) up to the front of the buffer.

If the integer can be negative, you need to remember that, make it positive by subtracting it from zero, and stick a '-' on the end before you reverse it.

Sounds a lot, but the long2str function takes about 20 lines.

Edit: there is a recursive solution too. Going down the required depth, and saving the digits on the way back out, avoids the reverse sequence issue (including the minus sign), and creates the string without padding.

//.. Recursive solution.

//.. Base case.

void l2s_r (char **p, long int n)

{
    char d = (n % 10) + '0';
    if (n >= 10) l2s_r (p, n / 10);
    *((*p)++) = d;
}

//.. Wrapper case.

char *l2s (long int n)

{
static char s[24];
char *p = s;

    if (n < 0) { *p++ = '-'; n = 0 - n; }
    l2s_r (& p, n);
    *p = '\0';
    return (s);
}
Sign up to request clarification or add additional context in comments.

7 Comments

I have checked on the itoa sources from glibc : they start with a pointer at the end of a buffer, then add each digit moving the pointer backward. See lines 80-82 from sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/generic/… Line 84, the pointer is just returned. There remains the sign handling. (Note, the function from the link is more complex because it deals with different bases and - when in hexadecimal - different cases).
@FrédéricLoyer Good Find. That is clumsy in a different way: it depends on the caller allocating a buffer of sufficient size, and passing in an end address, and a positive or unsigned value. It is not a user-level function -- first line says internal. The caller (presumably printf) needs to have a separate buffer for each arg value, or copy the string somewhere else for each value.
@Paul_Pedant it is fairly easy to know how many digits a number has. Just a couple of comparisons, which can be written in a 2-line cycle...
"but then you have to copy the whole string (including the NUL) up to the front of the buffer." --> Not really. Just print(buffer + offset_to_beginning).
"make it positive by subtracting it from zero" has a corner concern: when x == INT_MIN or the like, which 0-x leads to UB. Alternative: "make it negative by subtracting it from zero". The goal being to work from one side of the integer range. It does not have to be the positive side (although that is conceptually easier) and the negative side is one bigger. IAC, nice non-code answer.
|
1

Something like this

int length(long long int no) 
{
 int count = 0;
 while (no != 0) 
 {
    no = no/10;
        count++;
 }
 return count;
}

void printer(long long int no)
{
   long long int temp = no;
   int i=0;
   char arr[10000];    
   i = length(no)-1;
   //Extract digits and fill `arr` backwards
   while(temp > 0)
   {
        arr[i] = (temp%10 + '0');    
        i--;
        temp = temp/10;
   }
   arr[length(no)] = '\n'; 
  char* p = &arr[0];
  println(p); //specific to: https://github.com/SpinalHDL/VexRiscv/blob/master/src/main/c/murax/hello_world/src/main.c
  //return p;
}

1 Comment

Try printer(0) or printer(some negative value), no digits printed. char arr[10000]; excessively large. Missing return.
0

As its been a week, (don't like to provide code for late night HW assignments), so some code to augment @Paul_Pedant descriptive answer.

An approach, in base 2 to 36, that forms the numeric string right to left.

It avoids undefined behavior of 0 - INT_MIN by forming the negative absolute value.

It uses div() rather than /, % to avoid implementation behavior with C89.

A do loop is used so print_int(0, base) prints "0".

#include <assert.h>
#include <limits.h>
#include <stddef.h>
#include <stdlib.h>
extern int print(const char *);

// Enough room for INT_MIN as a binary string.
#define MY_ITOA_BUF_SIZE (sizeof(int)*CHAR_BIT + 2)
#define MY_ITOA_BASE_MIN 2
#define MY_ITOA_BASE_MAX 36

// Negative values result in a string beginning with a '-' for all bases.
// Adjust as desired.
void print_int(int i, int base) {
  char buf[MY_ITOA_BUF_SIZE];
  char *p = &buf[sizeof buf - 1]; // Start from the "right".
  assert(base >= MY_ITOA_BASE_MIN && base <= MY_ITOA_BASE_MAX);

  *p = '\0';
  int value = i < 0 ? i : -i;  // negative absolute value
  do {
    div_t qr = div(value, base);
    *(--p) = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[-qr.rem];  // Table look up
    value = qr.quot;
  } while (value);

  if (i < 0) {
    *(--p) = '-';
  }

  print(p);  // Like fputs(p, stdout)
}

2 Comments

How about using enum instead of macros?
@AyxanHaqverdili Yes, an enum could be used instead of a macro. Define an array with enum variable as array size.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.