Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Convert integer to string of English

16 views
Skip to first unread message

Simon Biber

unread,
Dec 14, 2001, 4:49:17 PM12/14/01
to
I've written this code to convert an integer to a string of English. It only
supports abs(n)<1000.

I'm a bit worried about my scanf usage in main(), it appears to work fine
but looks really weird. It allows stuff like the input line "get 23 and 34
then quit", from which it will interpret 23, 34, then the 'q' will make it
quit.

Some tips or corrections would be handy. (It's not an assignment, just
practise.)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* convert number to words */

char *numtoword (char *dest, int num)
{
char *units[] =
{
"zero", "one", "two", "three", "four", "five",
"six", "seven", "eight", "nine"
};
char *teens[] =
{
"ten", "eleven", "twelve", "thirteen", "fourteen",
"fifteen", "sixteen", "seventeen", "eighteen", "nineteen"
};
char *tens[] =
{
"", "", "twenty", "thirty", "forty", "fifty",
"sixty", "seventy", "eighty", "ninety"
};

*dest = '\0';

if (num<0)
{
strcat (dest, "minus ");
num = -num;
}

if (num > 999)
{
strcat (dest, "too big");
return dest;
}

if (num > 99)
{
strcat (dest, units[num / 100]);
strcat (dest, " hundred");
num = num % 100;
if (num != 0)
strcat(dest, " and ");
else
return dest;
}

if (10<=num && num<=19)
strcat(dest, teens[num % 10]);
else
{
strcat(dest, tens[num / 10]);
if (num%10 != 0 || num==0)
{
if (num > 20)
strcat(dest, "-");
strcat(dest, units[num % 10]);
}
}
return dest;
}

int main (void)
{
int i;
char words[100];

puts ("Enter integers, or q to exit.");

for(;;)
{
while (1 != scanf("%d", &i))
{
int c = getchar();
if (c == 'q' || c == EOF)
return 0;
}
if (numtoword(words, i))
printf ("%s\n", words);
else
break;
}
return 0;
}

--
Simon.


Dann Corbit

unread,
Dec 14, 2001, 5:48:01 PM12/14/01
to
Look at fmtmoney.c from snippets
--
C-FAQ: http://www.eskimo.com/~scs/C-faq/top.html
"The C-FAQ Book" ISBN 0-201-84519-9
C.A.P. FAQ: ftp://cap.connx.com/pub/Chess%20Analysis%20Project%20FAQ.htm


Kevin Miller

unread,
Dec 15, 2001, 11:45:08 AM12/15/01
to
Simon Biber wrote:
>
> I've written this code to convert an integer to a string of English. It only
> supports abs(n)<1000.
>
> I'm a bit worried about my scanf usage in main(), it appears to work fine
> but looks really weird. It allows stuff like the input line "get 23 and 34
> then quit", from which it will interpret 23, 34, then the 'q' will make it
> quit.
>
> Some tips or corrections would be handy. (It's not an assignment, just
> practise.)
>

> char *numtoword (char *dest, int num)
> {

(snip)

> *dest = '\0';
>
> if (num<0)
> {
> strcat (dest, "minus ");
> num = -num;
> }
>
> if (num > 999)
> {
> strcat (dest, "too big");
> return dest;
> }

This will fail on many machines if num equals INT_MIN.
INT_MIN is a negative number, but (-INT_MIN) is NOT usually
representable as an int on a two's complement machine, and
in such a case, computing (-INT_MIN) produces undefined
behavior. Hence, I would replace the code abovce with
something like this:

*dest = '\0';

if (num > 999 || num < -999)
{
strcat (dest, "magnitude too big");
return dest;
}


if (num<0)
{
strcat (dest, "minus ");
num = -num;
}

As regards the following:

> if (numtoword(words, i))
> printf ("%s\n", words);
> else
> break;

Your function "numtoword" always returns a non-null pointer, so
it's not clear to me what this test is for. Is this intended
to handle a future modification where "numtoword" would return
NULL (as opposed to "too big") if it received an input that was
too large?

Simon Biber

unread,
Dec 15, 2001, 12:44:46 PM12/15/01
to
"Kevin Miller" <erase.1st.19.c...@earthlink.net> wrote:
[snip]

> This will fail on many machines if num equals INT_MIN.
> INT_MIN is a negative number, but (-INT_MIN) is NOT usually
> representable as an int on a two's complement machine, and
> in such a case, computing (-INT_MIN) produces undefined
> behavior. Hence, I would replace the code abovce with
> something like this:
[snip]

Thanks, that's a good catch. My new code catches that (it's the only thing
it catches now).

> Your function "numtoword" always returns a non-null pointer, so
> it's not clear to me what this test is for. Is this intended
> to handle a future modification where "numtoword" would return
> NULL (as opposed to "too big") if it received an input that was
> too large?

Originally I did make it return NULL when it was out of range. But for
testing purposes I wanted to be able to see what had been put in the string
already even if it did bail out, so I decided to return dest anyway.

But now it only returns NULL if you give a negative number which can't be
negated.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <math.h>
#include <limits.h>

/* convert number to words */

static void add0to999(char *dest, long num)


{
char *units[] =
{
"zero", "one", "two", "three", "four", "five",
"six", "seven", "eight", "nine"
};
char *teens[] =
{
"ten", "eleven", "twelve", "thirteen", "fourteen",
"fifteen", "sixteen", "seventeen", "eighteen", "nineteen"
};
char *tens[] =
{
"", "", "twenty", "thirty", "forty", "fifty",
"sixty", "seventy", "eighty", "ninety"
};

if (num > 99)


{
strcat (dest, units[num / 100]);
strcat (dest, " hundred");
num = num % 100;
if (num != 0)
strcat(dest, " and ");
else

return;
}

if (10<=num && num<=19)
strcat(dest, teens[num % 10]);
else
{
strcat(dest, tens[num / 10]);
if (num%10 != 0 || num==0)
{
if (num > 20)
strcat(dest, "-");
strcat(dest, units[num % 10]);
}
}
}

char *numtowords (char *dest, long num)
{
char *mult[] = {" billion ", " million ", " thousand "};
int i, n = sizeof mult / sizeof *mult;

/* We can't negate LONG_MIN if it's less than -LONG_MAX */
if (num < -LONG_MAX) return NULL;

/* Empty string */
*dest = '\0';

/* Handle negative numbers */


if (num<0)
{
strcat (dest, "minus ");
num = -num;
}

/* Iterate through multipliers */
for (i=0; i<n; i++)
{
long x = pow(1000, n-i);
if (num >= x)
{
add0to999(dest, num/x);
strcat(dest, mult[i]);
num = num % x;
if (num == 0) return dest;
if (num < 99) strcat(dest, "and ");
}
}

/* The final 3 digits */
add0to999(dest, num);

return dest;
}

int main (void)
{
long i;
char words[200];

puts ("Enter integers, or q to exit.");

for(;;)
{
while (1 != scanf("%ld", &i))


{
int c = getchar();
if (c == 'q' || c == EOF)
return 0;
}

if (numtowords(words, i))


printf ("%s\n", words);
else

printf ("Magnitude too large\n");
}
return 0;
}

--
Simon.


David Rubin

unread,
Dec 17, 2001, 1:26:13 PM12/17/01
to
Simon Biber wrote:

[snip - code for num2text]

Your program is pretty good. The only complaint I have about it is that
"987" should be "nine hundred eighty-seven" and not "nine hundred AND
eighty-seven." At least this is what I learned in school, although
common usage might be otherwise.

I had written a similar program like this myself, but the goal was to
translate dollar amounts to text (for speech synthesis?). To get around
the problem of knowing how large the number is (i.e., powers of ten), I
build the text string from left to right by reversing the text at each
step and then reversing the final result. The function amount() does
this, but as I look back on it, I wish I had commented a little more;
it's a bit intricate.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define NUL '\0'
#define ZERO ones[0]
#define ONE ones[1]

enum {
MAXDIGITS = 15 /* largest whole currency amount */
};

char *reverse(char *);
char *amount(char *);
int valid(char *);

char *ones[] = {


"zero",
"one",
"two",
"three",
"four",
"five",
"six",
"seven",
"eight",

"nine",


"ten",
"eleven",
"twelve",
"thirteen",
"fourteen",
"fifteen",
"sixteen",
"seventeen",
"eighteen",

"nineteen",
};

char *tens[] = {
0,
0,


"twenty",
"thirty",
"forty",
"fifty",
"sixty",
"seventy",
"eighty",

"ninety",
};

char *hundreds[] = {
" ",
" thousand",
" million",
" billion",
" trillion",
};

char zeros[MAXDIGITS+1];
char *argv0;

void
usage(void)
{
fprintf(stderr, "usage: %s amount...\n", argv0);
exit(1);
}

int
valid(char *amt)
{
size_t n;
char *p, *e;

/* bad decimal amount or size */
e = amt + strlen(amt);
if(p=strrchr(amt, '.')){
if(e - p != 3)
return 0;
if(p - amt > MAXDIGITS)
return 0;
}else if(e - amt > MAXDIGITS)
return 0;

/* bad character */
while(n=strspn(amt, "1234567890."))
amt += n;
return *amt == 0;
}

char *
reverse(char *s)
{
static char buf[BUFSIZ];
char *e, *p;

p = buf;
e = s + strlen(s) - 1;
while(e >= s)
*p++ = *e--;
*p = NUL;
return buf;
}

char *
amount(char *amt)
{
char buf[BUFSIZ]={0};
char *p;
size_t n; /* numerals to process */
int v, /* value */
h=0, /* hundred? */
m=0; /* magnitude */
int k, offset[] = {2, 1, 1};

n = strlen(amt);

/* return "zero" on all zeros */
if(!strncmp(amt, zeros, n))
return ones[0];

p = amt + n;
while(n){
k = offset[(n == 1) + h];
p -= k;
if(h % 2){
v = atoi(p) % 10;
if(v)
strcat(buf, reverse(" hundred"));
m++;
h=0;
}else{
strcat(buf, reverse(hundreds[m]));
v = atoi(p) % 100;
h=1;
}
*p = NUL;
n -= k;

if(v){
if(v < 20)
strcat(buf, reverse(ones[v]));
else{
if(v%10){
strcat(buf, reverse(ones[v%10]));
strcat(buf, "-");
}
strcat(buf, reverse(tens[v/10]));
}
if(n)
strcat(buf, " ");
}
}
return reverse(buf+1);
}

int
main(int argc, char *argv[])
{
char *p, *s;
char buf[BUFSIZ];

argv0 = argv[0];
if(argc == 1)
usage();

memset(zeros, '0', sizeof zeros - 1);
zeros[sizeof zeros - 1] = NUL;

for(argv++; *argv; argv++){
if(valid(*argv)){
strcpy(buf, *argv);
if(p=strrchr(buf, '.'))
*p = NUL;

s = p == buf ? ZERO : amount(buf);
printf("%s dollar%s", s, strcmp(s, ONE) ? "s" : "");

s = p == 0 ? ZERO : amount(p+1);
printf(" and %s cent%s\n", s, strcmp(s, ONE) ? "s" : "");
}else
fprintf(stderr, "invalid: %s\n", *argv);
}
return 0;
}

david

--
If 91 were prime, it would be a counterexample to your conjecture.
-- Bruce Wheeler

Simon Biber

unread,
Dec 17, 2001, 3:54:12 PM12/17/01
to
"David Rubin" <dlr...@hotmail.com> wrote:
> Simon Biber wrote:
>
> [snip - code for num2text]
>
> Your program is pretty good. The only complaint I have about it is that
> "987" should be "nine hundred eighty-seven" and not "nine hundred AND
> eighty-seven." At least this is what I learned in school, although
> common usage might be otherwise.

I believe this is a regional difference. At school here in Australia I was
taught it should be "nine hundred and eighty-seven". Often we shorten it to
"nine eighty-seven" but we never have "hundred" without "and".

> I had written a similar program like this myself, but the goal was to
> translate dollar amounts to text (for speech synthesis?). To get around
> the problem of knowing how large the number is (i.e., powers of ten), I
> build the text string from left to right by reversing the text at each
> step and then reversing the final result. The function amount() does
> this, but as I look back on it, I wish I had commented a little more;
> it's a bit intricate.

[snip code]

It's bugged... If there is nothing in a position you should leave out the
qualifier!

C:\clc\drubin>a 1000000000
one billion million thousand dollars and zero cents
(should be one billion dollars)

C:\clc\drubin>a 1000000
one million thousand dollars and zero cents
(should be one million dollars)

--
Simon.


David Rubin

unread,
Dec 17, 2001, 4:01:37 PM12/17/01
to
Simon Biber wrote:

> [snip code]
>
> It's bugged... If there is nothing in a position you should leave out the
> qualifier!
>
> C:\clc\drubin>a 1000000000
> one billion million thousand dollars and zero cents
> (should be one billion dollars)
>
> C:\clc\drubin>a 1000000
> one million thousand dollars and zero cents
> (should be one million dollars)

Quite right! Thanks.

Dann Corbit

unread,
Dec 17, 2001, 4:52:24 PM12/17/01
to
/*
I messed with it a bit...

No more accurate than LDBL_DIG significant digits.

fmtmoney 999999999.88
fmt_money(1e+009) = Nine Hundred Ninety-Nine Million Nine Hundred
Ninety-Nine Thousand Nine Hundred Ninety-Nine & 88/100
*/


/* +++Date last modified: 02-Nov-1995 */

/*
** FMTMONEY.C - Format a U.S. dollar value into a numeric string
**
** public domain demo by Bob Stout
*/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <float.h>

#define Form(s,a) bufptr += sprintf(bufptr, s, a)

static char buf[1024],
*bufptr;

static const char *units[] = {
"Zero", "One", "Two", "Three", "Four",
"Five", "Six", "Seven", "Eight", "Nine",
"Ten", "Eleven", "Twelve", "Thirteen", "Fourteen",
"Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen"
};

static const char *tens[] = {
"Twenty", "Thirty", "Forty", "Fifty", "Sixty", "Seventy", "Eighty",
"Ninety"
};

typedef struct tag_DigitGroups {
long double dpower;
char *pszName;
} DigitGroups;

static const DigitGroups PowersOfOneThousand[] = {
{1e00, ""},
{1e03, "Thousand"},
{1e06, "Million"},
{1e09, "Billion"},
{1e12, "Trillion"},
{1e15, "Quadrillion"},
{1e18, "Quintillion"},
{1e21, "Sextillion"},
{1e24, "Septillion"},
{1e27, "Octillion"},
{1e30, "Nonillion"},
{1e33, "Decillion"},
{1e36, "Undecillion"},
{1e39, "Duodecillion"},
{1e42, "Tredecillion"},
{1e45, "Quattuordecillion"},
{1e48, "Quindecillion"},
{1e51, "Sexdecillion"},
{1e54, "Septendecillion"},
{1e57, "Octodecillion"},
{1e60, "Novemdecillion"},
{1e63, "Vigintillion"}
};

static void form_group(int, char *);

/*
** Call with long double amount
** Rounds cents
** Returns string in a static buffer
*/

char *fmt_money(long double amt)
{
int temp;
int i;
int topgroup;
long double dummy;
long double cents = modfl(amt, &dummy);
long double powerof10;

*buf = '\0';
bufptr = buf;
if (amt < 0) {
amt = -amt;
*bufptr = '-';
bufptr++;
*bufptr = 0;
}
powerof10 = log10l(amt);
topgroup = ((int) powerof10) / 3;
if (topgroup > 21) {
puts("Error! Unable to process numbers that large.\n");
exit(EXIT_FAILURE);
}
if (powerof10 > LDBL_DIG) {
puts("Warning! Loss of accuracy in calculation.\n");
}
for (i = topgroup; i > 0; i--) {
temp = (int) (amt / PowersOfOneThousand[i].dpower);
if (temp) {
form_group(temp, PowersOfOneThousand[i].pszName);
amt = fmodl(amt, PowersOfOneThousand[i].dpower);
}
}
form_group((int) amt, "");

if (buf == bufptr)
Form("%s ", units[0]);

temp = (int) (cents * 100. + .5);
sprintf(bufptr, "& %02d/100", temp);

return buf;
}

/*
** Process each thousands group
*/

static void form_group(int amt, char *scale)
{
if (buf != bufptr)
*bufptr++ = ' ';

if (100 <= amt) {
Form("%s Hundred ", units[amt / 100]);
amt %= 100;
}
if (20 <= amt) {
Form("%s", tens[(amt - 20) / 10]);
if (0 != (amt %= 10)) {
Form("-%s ", units[amt]);
} else
Form("%s", " ");
} else if (amt) {
Form("%s ", units[amt]);
}
Form("%s", scale);
}

#ifdef TEST

main(int argc, char *argv[])
{

while (--argc) {
long double amt = atof(*(++argv));
printf("fmt_money(%Lg) = %s\n", amt, fmt_money(amt));
}
return EXIT_SUCCESS;
}

#endif /* TEST */

0 new messages