[net.sources] Programs for computing bicycle gearings

lat@stcvax.UUCP (06/01/84)

     Included you will find the sources for two programs that provide
information about bicycle gearing.  The first part is the only
documentation I have.  The sources come last.

     The `gears' program asks you for the number of cogs on your chain
wheels (usually 2 wheels, sometimes 3, but it will accept up
to 4) the number of cogs on each free-wheel (usually you'll have 5
gears, sometimes 6; 8 is the max) and your wheel size.  It then prints
out a chart like the one below.  The program prompts with `: ' when
it's waiting for a value.  The cross chain combinations are not used.

	Enter chain wheel sizes (# teeth), one per line.
	Type RETURN when done.
	: 53
	: 41
	:

	Enter free wheel sizes (# teeth), one per line.
	Type RETURN when done.
	: 28
	: 24
	: 20
	: 17
	: 14
	:
	Wheel size (inches): 27

	Chain Wheel:   53  41
	Free Wheel:    28  24  20  17  14


	Chain Wheel	Free Wheel	Ratio	      Diff

	      41	     28   	 39.5 	      ----
	      41	     24   	 46.1 	       6.6
	      41	     20   	 55.3 	       9.2
	      53	     24   	 59.6 	       4.3
	      41	     17   	 65.1 	       5.5
	      53	     20   	 71.6 	       6.4
	      53	     17   	 84.2 	      12.6
	      53	     14   	102.2 	      18.0

     The `setup' program gives you help in choosing a set of gears.
It prompts you for a bunch of values.  Here are the prompts you will
see.  Values in parentheses are default values selected if you type
just return.

	Highest gear ratio in gear inches:
	Lowest gear ratio in gear inches:
	Max allowed gear spacing (25):
	# of chain wheels (2):
	Max # teeth on chain wheels (54):
	Min # teeth on chain wheels (35):
	# of free wheel gears (5):
	Max # teeth on free wheel gears (34):
	Min # teeth on free wheel gears (13):
	Wheel size (27):

     The first two values are so you can better tailor your gearing
to your environment.  If you live in Kansas you can probably get away
with high gearing.  To get a set of gears similar to the ones in the
chart above, you could type 102 for the 1st value and 39 for the 2nd.

     The 3rd prompt is so that the program doesn't give any bizarre
combinations with huge spacings between gears.  I added it because
the 1st pass at the program produced a gearing where the spacing
between the two highest gears was almost 35 gear-inches!

	The next 6 prompts describe your gearing.  The program asks for
the max & min # of teeth because derailleurs (good word for a spelling
bee) are limited in what they can handle here.  If you don't know
about your bike, ask your local bike shop.

	Ok, here come the source files, in shar format.  Cut at the
dashed line.  Don't forget the dashed line before my .signature.
------------------------------
cat > makefile << End_Of_makefile
all:	gears setup

gears:	gears.o gearset.o prompt.o
	cc -o gears gears.o gearset.o prompt.o

setup:	setup.o gearset.o prompt.o
	cc -o setup setup.o gearset.o prompt.o
End_Of_makefile
cat > gears.c << End_Of_gears.c
#include <stdio.h>

#define NFS 4
#define NFW 8

int chainw[NFS];
int freew[NFW];
float wheelsz;

/* ARGSUSED */
main(argc, argv)
char **argv;
{
	register int ng, nf;
	register int r;

	fprintf(stderr, "Enter chain wheel sizes (# teeth), one per line.\n");
	fprintf(stderr, "Type RETURN when done.\n");

	for (ng = 0; ng < NFS; ng++)  {
		if ((r = getint(":", &chainw[ng])) == 0)
			break;
		if (r < 0)		/* Error */
			ng--;
	}
	if (ng == 0)  {
		fprintf(stderr, "Let's be reasonable\n");
		exit(0);
	}
	if (ng >= NFS)
		fprintf(stderr, "No more chain wheels allowed\n");

	fprintf(stderr, "Enter free wheel sizes (# teeth), one per line.\n");
	fprintf(stderr, "Type RETURN when done.\n");

	for (nf = 0; nf < NFW; nf++)  {
		if ((r = getint(":", &freew[nf])) == 0)
			break;
		if (r < 0)		/* Error */
			nf--;
	}
	if (nf == 0)  {
		fprintf(stderr, "Let's be reasonable\n");
		exit(0);
	}
	if (nf >= NFW)
		fprintf(stderr, "No more free wheel gears allowed\n");

	wheelsz = 27;		/* Assume default wheel size */
	getflt("Wheel size (inches):", &wheelsz);
	gearset(&chainw[0], ng, &freew[0], nf, wheelsz, 1);
	exit(0);
}
End_Of_gears.c
cat > gearset.c << End_Of_gearset.c
#include <stdio.h>

#define NRATIOS 100

struct gearset {
	int	chgear,
		freegear;
	int	badmatch;
	float	gearinch;
} ratios[NRATIOS];

gcompar(s1, s2)
struct gearset *s1, *s2;
{	if (s1->gearinch < s2->gearinch)
		return -1;
	if (s1->gearinch > s2->gearinch)
		return 1;
	return 0;
}

intcmp(i, j)
int *i, *j;
{
	return (*j - *i);
}

gearset(fs, nfs, fw, nfw, wsz, ignore)
int *fs, nfs, *fw, nfw;
float wsz;
{
	register struct gearset *gp;
	register char *dp;
	int i, j, k;
	struct gearset *gpend;
	float prev;
	char diffbuf[20];

	/* Sort and print the chain wheels. */
	qsort(fs, nfs, sizeof(int), intcmp);
	printf("\n\n");
	printf("Chain Wheel: ");
	for (i = 0; i < nfs; i++)
		printf("  %2d", fs[i]);
	printf("\n");

	/* Sort and print the free wheel ratios. */
	qsort(fw, nfw, sizeof(int), intcmp);
	printf("Free Wheel:  ");
	for (i = 0; i < nfw; i++)
		printf("  %2d", fw[i]);
	printf("\n");
	k = 0;
	for (i = 0; i < nfs; i++)  {
		for (j = 0; j < nfw; j++)  {
			if (k >= NRATIOS)  {
				fprintf(stderr, "More than %d gear combinations!\n",
					NRATIOS);
				exit(1);
			}
			ratios[k].chgear = fs[i];
			ratios[k].freegear = fw[j];
			ratios[k].gearinch = (float)wsz * fs[i] / fw[j];

			/* Flag biggest chain wheel with biggest free wheel
			 * and smallest chain wheel with smallest free wheel.
			 */
			ratios[k].badmatch = 0;
			if (i == 0  &&  j == 0  || i == nfs-1  &&  j == nfw-1)  {
				if (ignore)
					continue;
				ratios[k].badmatch = 1;
			}
			k++;
		}
	}
	qsort((char*)ratios, k, sizeof(ratios[0]), gcompar);

	printf("\n\n");
	printf("Chain Wheel	Free Wheel	Ratio	      Diff\n\n");

	gpend = &ratios[k];
	prev = 0.0;
	for (gp = &ratios[0]; gp < gpend; gp++)  {
		if (prev == 0.0)
			dp = "----";
		else  {
			dp = diffbuf;
			sprintf(dp, "%4.1f", gp->gearinch - prev);
		}
		printf("%8d	%7d   	%5.1f 	%s %s\n",
			gp->chgear, gp->freegear, gp->gearinch,
			gp->badmatch ? "*****" : "     ", dp);
		prev = gp->gearinch;
	}
}
End_Of_gearset.c
cat > prompt.c << End_Of_prompt.c
#include <stdio.h>

getint(ps, num)
char *ps;
int *num;
{
	char line[82];

	fprintf(stderr, "%s ", ps);
	if (fgets(line, 80, stdin) == NULL)  {
		exit(0);
	}
	line[80] = '\0';
	if (strlen(line) <= 1)  {
		return 0;
	}
	if (sscanf(line, "%d", num) != 1)  {
		fprintf(stderr, "Bad number, try again.\n");
		return -1;
	}
	return 1;
}

getflt(ps, num)
char *ps;
float *num;
{
	char line[82];

	fprintf(stderr, "%s ", ps);
	if (fgets(line, 80, stdin) == NULL)  {
		exit(0);
	}
	line[80] = '\0';
	if (strlen(line) <= 1)  {
		return 0;
	}
	if (sscanf(line, "%f", num) != 1)  {
		fprintf(stderr, "Bad number, try again.\n");
		return -1;
	}
	return 1;
}

getyn(ps, ans)
char *ps;
int *ans;
{
	char line[82];

	fprintf(stderr, "%s (y/n) ", ps);
	if (fgets(line, 80, stdin) == NULL)  {
		exit(0);
	}
	line[80] = '\0';
	if (strlen(line) <= 1)  {
		return 0;
	}
	if (line[0] == 'Y'  ||  line[0] == 'y')  {
		*ans = 1;
		return 1;
	}
	if (line[0] == 'N'  ||  line[0] == 'n')  {
		*ans = 0;
		return 1;
	}
	return -1;
}
End_Of_prompt.c
cat > setup.c << End_Of_setup.c
/* This program determines bicycle gear sets which
 * maximize the spacing between gears.
 *
 * Note: you might want to set your tab stops to 5.
 */

#include <stdio.h>

#define NO 0
#define YES 1

/* Max # of chain wheels */
#define NFS 4
static int chwheels[NFS];

/* Max # of free wheels */
#define NFW 8
static int frwheels[NFW];

/* Max and min # of teeth on front sprocket */
#define MAXFST 54
#define MINFST 35

/* Max and min # of teeth on free wheel */
#define MAXFWT 34
#define MINFWT 13

static float upper, lower;	/* Upper and lower gear ranges */
static int nfs;			/* # of chain wheels */
static int nfw;			/* # of free wheels */
static float whlsize;		/* Wheel size */
static int mgspace;			/* Max allowed spacing between gears */
static int maxfst, minfst;	/* Max & min # teeth on chain wheels */
static int maxfwt, minfwt;	/* Max & min # teeth on free wheels */

static int lfs, sfs;		/* Large & small # chain wheels */
static int lfw, sfw;		/* Large & small # free wheels */
static int nfound;

/* This structure contains a set of chain wheels, free wheel,
 * and the closest spacing between any two gears.  We keep the
 * NBEST best choices and discard the rest.
 */
struct gearing {
	int	 g_fs[NFS];		/* Choices for chain wheels */
	int	 g_fw[NFW];		/* Choices for free wheel */
	float g_minspace;		/* Minimum spacing between two gears */
	struct gearing *g_next;	/* Points to next entry */
};

#define NBEST 10
static struct gearing gears[NBEST];

static int ng_left = NBEST;		/* # unused entries remaining in choices */
static struct gearing *gp_head, *gp_tail;

static int ccignore;		/* Ignore cross chain ratios */

main(argc, argv)
{
	register struct gearing *gp;
	float whinch;
	double fabs();
	char fmt[80];

	for ( ;; )  {
		while (getflt("Highest gear ratio in gear inches:", &upper) <= 0)
			;
		while (getflt("Lowest gear ratio in gear inches:", &lower) <= 0)
			;
		if (lower < upper)
			break;
		fprintf(stderr, "Lowest gear > than highest gear, bozo!\n");
	}

	mgspace = 25;
	while (getint("Max allowed gear spacing (25):", &mgspace) < 0)
		;

	for ( ;; )  {
		nfs = 2;
		while (getint("# of chain wheels (2):", &nfs) < 0)
			;
		if (nfs < 2)  {
			fprintf(stderr, "Must be at least 2");
		} else if (nfs > NFS)  {
			fprintf(stderr, "Can't be more than %d", NFS);
		} else {
			break;
		}
		fputs(", try again\n", stderr);
	}

	maxfst = MAXFST;
	sprintf(fmt, "Max # teeth on chain wheels (%d):", MAXFST);
	while (getint(fmt, &maxfst) < 0)
		;

	minfst = MINFST;
	sprintf(fmt, "Min # teeth on chain wheels (%d):", MINFST);
	while (getint(fmt, &minfst) < 0)
		;

	for ( ;; )  {
		nfw = 5;
		while (getint("# of free wheel gears (5):", &nfw) < 0)
			;
		if (nfw < 2)  {
			fprintf(stderr, "Must be at least 2");
		} else if (nfw > NFW)  {
			fprintf(stderr, "Can't be more than %d", NFW);
		} else {
			break;
		}
		fputs(", try again\n", stderr);
	}

	maxfwt = MAXFWT;
	sprintf(fmt, "Max # teeth on free wheel gears (%d):", MAXFWT);
	while (getint(fmt, &maxfwt) < 0)
		;

	minfwt = MINFWT;
	sprintf(fmt, "Min # teeth on free wheel gears (%d):", MINFWT);
	while (getint(fmt, &minfwt) < 0)
		;

	for ( ;; )  {
		whlsize = 27.0;
		while (getflt("Wheel size (27):", &whlsize) < 0)
			;
		if (whlsize > 0)
			break;
		fprintf(stderr, "Let's be reasonable");
		fputs(", try again\n", stderr);
	}

	ccignore = YES;
/*
 *	while (getyn("Ignore cross-chain gear combinations?", &ccignore) < 0)
 *		;
 */

	/* Work through the large-front-sprocket/small-free-wheel combos */
	for (lfs = maxfst; lfs > minfst; lfs--)  {
		for (sfw = minfwt; sfw < maxfwt; sfw++)  {
			whinch = whlsize * lfs / sfw;
			if (fabs(whinch - upper) > 0.5)	/* Out of bounds */
				continue;
			chwheels[0] = lfs;
			frwheels[nfw-1] = sfw;
			/* Small-front-sprocket/large-free-wheel combos */
			for (sfs = minfst; sfs < lfs; sfs++)  {
				for (lfw = maxfwt; lfw > sfw; lfw--)  {
					whinch = whlsize * sfs / lfw;
					if (fabs(whinch - lower) > 0.5)
						continue;
					chwheels[nfs-1] = sfs;
					frwheels[0] = lfw;
					/* Recurse looking for front sprockets */
					fsrecurs(lfs-1, sfs+1, 1);
				}
			}
		}
	}

	if ((gp = gp_head) == NULL)  {
		fprintf(stderr, "Couldn't find any with those parameters\n");
	} else {
		printf("Parameters:\n");
		printf("\tGear range: %1.0f - %1.0f\n", lower, upper);
		printf("\tWheel size: %3.1f\n", whlsize);
		printf("\tMax allowed gear spacing: %d\n", mgspace);
		printf("\tCross-chain gear combinations %s\n\n",
			ccignore ? "ignored" : "included");
		do  {
			gearset(gp->g_fs, nfs, gp->g_fw, nfw, whlsize, ccignore);
		} while ((gp = gp->g_next) != NULL);
	}
	exit(0);
}

fsrecurs(ub, lb, ix)
int ub, lb;			/* Upper & lower bounds for undetermined gears */
register int ix;		/* Index into chwheels where new one goes */
{
	register int i;

	/* The large & small gears are already chosen; thus the `2' */
	if (ix+2 <= nfs)  {
		register int nteeth;

		i = ix++;
		for (nteeth = ub; nteeth >= lb; )  {
			chwheels[i] = nteeth--;
			fsrecurs(nteeth, lb, ix);
		}
		return;
	}
	/* We get to here when we have a full chain wheel set.
	 * We can recurse on the choices of free wheel gears.
	 */
	fwrecurs(lfw-1, sfw+1, 1);
}

fwrecurs(ub, lb, ix)
int ub, lb;			/* Upper & lower bounds for undetermined gears */
register int ix;		/* Index into frwheels where new one goes */
{
	register int i, j;
	register int gix;		/* Index into ginches */
	float ginches[NFS*NFW];
	float minspace, maxspace, diff;
	register struct gearing *gp, **gpp;
	int fcompar();

	/* The large & small gears are already chosen. Thus the `2' below */
	if (ix+2 <= nfw)  {
		register int nteeth;

		i = ix++;
		for (nteeth = ub; nteeth >= lb; )  {
			frwheels[i] = nteeth--;
			fwrecurs(nteeth, lb, ix);
		}
		return;
	}

	/* Get to here when we have a full free wheel gear set.
	 * Compute all the gear-inch values for all combinations.
	 * Sort them into order, find the smallest spacing between
	 * adjacent gears, and put this entry into the list of
	 * good choices.
	 */
	gix = 0;
	for (i = 0; i < nfs; i++)  {
		for (j = 0; j < nfw; j++)  {
			/* Don't use this value if it's one of those
			 * cross-chain combos, unless they want us to.
			 */
			if (ccignore && i == 0 && j == 0 || i == nfs-1 && j == nfw-1)
				continue;
			ginches[gix++] = whlsize * chwheels[i] / frwheels[j];
		}
	}
	qsort(ginches, gix, sizeof(float), fcompar);
	minspace = 100000.0;
	maxspace  = 0.0;

	for (i = 1; i < gix; i++)  {
		diff = ginches[i] - ginches[i-1];
		if (diff < minspace)
			minspace = diff;
		if (diff > maxspace)
			maxspace = diff;
	}
#ifdef DEBUG
printf("trying\n    ");
	for (i = 0; i < nfs; i++)  {
		printf("  %2d", chwheels[i]);
	}
	printf("\t\t");
	for (i = 0; i < nfw; i++)  {
		printf("  %2d", frwheels[i]);
	}
	printf("\t%5.2f\t%5.2f\n", minspace, maxspace);
#endif
	if (maxspace > mgspace)  {
#ifdef DEBUG
		printf("Too big\n");
#endif
		return;
	}

	/* Now see if this combination is a candidate. */
	gpp = &gp_head;
	while ((gp = *gpp) != NULL)  {
		if (gp->g_minspace < minspace)  {
			break;
		}
		gpp = &gp->g_next;
	}
	if (gp == NULL)  {
		/* This gearing set has the smallest difference.  If
		 * we haven't chosen our ten best yet, we can add
		 * this one, otherwise we just discard it.  Note
		 * that gp == NULL if it's discarded.
		 */
		if (ng_left > 0)  {
			gp = &gears[--ng_left];
			gp_tail = gp;
			*gpp = gp;
		}
	} else {
		/* Insert new entry.  We can either add a new one
		 * from the free list or discard the smallest entry.
		 */
		if (ng_left > 0)  {
			*gpp = &gears[--ng_left];
			gears[ng_left].g_next = gp;
			gp = *gpp;
		} else if (gp != gp_tail)  {
			/* We have to discard the smallest entry and
			 * use it's space for this new entry.
			 */
			*gpp = gp_tail;
			(*gpp)->g_next = gp;
			while (gp->g_next != gp_tail)  {
				gp = gp->g_next;
			}
			gp_tail = gp;		/* New smallest entry. */
			gp->g_next = NULL;
			gp = *gpp;
		}
	}
	/* Fill in the gear choices if we didn't discard */
	if (gp != NULL)  {
		gp->g_minspace = minspace;
		for (i = 0; i < nfs; i++)  {
			gp->g_fs[i] = chwheels[i];
		}
		for (i = 0; i < nfw; i++)  {
			gp->g_fw[i] = frwheels[i];
		}
#ifdef DEBUG
		for (gp = gp_head; gp != NULL; gp = gp->g_next)  {
			printf("[%2d]", gp - &gears[0]);
			for (i = 0; i < nfs; i++)  {
				printf("  %2d", gp->g_fs[i]);
			}
			printf("\t\t");
			for (i = 0; i < nfw; i++)  {
				printf("  %2d", gp->g_fw[i]);
			}
			printf("\t%5.2f\n", gp->g_minspace);
		}
		printf("\n\n");
#endif DEBUG
	}
}

/* Compare two floating point numbers for qsort(). */
fcompar(f1, f2)
float *f1, *f2;
{
	if (*f1 < *f2)
		return -1;
	if (*f1 > *f2)
		return 1;
	return 0;
}
End_Of_setup.c
------------------------------
-- 
{decvax, hao}!stcvax!lat				Larry Tepper
{allegra, amd70, ucbvax}!nbires!stcvax!lat		303-673-5435