/*
** BASE.C   Handles float to ASCII and ASCII to float conversion
**	    in any number base.
** http://willus.com, 11-14-92, ANSI C
**
** Eval is a floating point expression evaluator.
** This file last updated in version 1.10
** For the version number, see eval.h
** Copyright (C) 1993-2003  http://willus.com
**
** This program is free software; you can redistribute it and/or modify it
** under the terms of the GNU General Public License as published by the
** Free Software Foundation; either version 2 of the License, or any
** later version.
**
** This program is distributed in the hope that it will be useful, but
** WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
** General Public License for more details.
**
** You should have received a copy of the GNU General Public License along
** with this program; if not, write to the Free Software Foundation, Inc.,
** 675 Mass Ave, Cambridge, MA 02139, USA.
**
*/

#include "eval.h"
#include <float.h>

#define SCI	1
#define FREE	3

static int  sigfig;
static int  maxexp;
static int  fixed;
static int  sci;
static int  dplace;
static int  maxdigits;
static int  ibase;
static int  obase;

static void sci_out(double val,char *buf);
static void intadd(int x);
static void free_out(double val,char *buf);
static int  digval(char c);
static int  ascii_int(int base,char *s);
static void addtobuf(char *s);
static void initbuf(char *s);
static void incbuf(char c);


void baseconv(double val,char *buf)

    {
    if (sci)
	sci_out(val,buf);
    else
	free_out(val,buf);
    }


void set_scinote(int val)

    {
    sci=val;
    }


void set_sigfig(int val)

    {
    sigfig=val;
    }


void set_dplace(int val)

    {
    dplace=val;
    }


void set_maxexp(int val)

    {
    maxexp=val;
    }


void set_fix(int val)

    {
    fixed=val;
    }


void setobase(int base)

    {
    obase=base;
    maxdigits=precision(base);
    }


void setibase(int base)

    {
    ibase=base;
    }


int getobase(void)

    {
    return(obase);
    }


int getibase(void)

    {
    return(ibase);
    }


int get_scinote(void)

    {
    return(sci);
    }


int get_fix(void)

    {
    return(fixed);
    }


int precision(int base)

    {
    return((int)(DBL_MANT_DIG*log(2.)/log((double)base)));
    }


void print_outtype(void)

    {
    printf("\nScientific notation:       %s\n",sci ? "ON" : "OFF");
    printf("Significant digits:        ");
    if (sigfig>0)
	printf("%d",sigfig);
    else
	printf("max");
    if (!sci && fixed)
	printf(" (ignored)");
    printf("\n");
    printf("Fixed precision:           %s",fixed ? "ON" : "OFF");
    if (sci)
	printf(" (trailing zeroes printed.)");
    printf("\n");
    printf("Digits after the decimal:  %d",dplace);
    if (sci || !fixed)
	printf(" (ignored)");
    printf("\n");
    printf("Exponent limit:            ");
    if (maxexp<0)
	printf("none");
    else
	printf("%d",maxexp);
    if (sci)
	printf(" (ignored)");
    printf("\n\n");
    }


double asciiconv(int base,char *s)

    {
    double  val;
    int     i,div,expc;

    for (i=0;s[i]!=EOS;i++)
	s[i]=tolower((int)s[i]);
    expc= base>14 ? '\\' : 'e';
    div=-1;
    val=0;
    for (i=0;s[i]!=expc && s[i]!='\\' && s[i]!=EOS;i++)
	{
	if (s[i]=='.')
	    {
	    div++;
	    continue;
	    }
	if (div>=0)
	    div++;
	val=val*base+digval(s[i]);
	}
    if (div<0)
	div=0;
    if (s[i]!=EOS)
	div-=ascii_int(base,&s[i+1]);
    if (div!=0)
	val=val/pow((double)base,(double)div);
    return(val);
    }


static int ascii_int(int base,char *s)

    {
    int     i,val,sign;

    val=0;
    if (s[0]=='-' || s[0]=='+')
	{
	sign=(s[0]=='+');
	i=1;
	}
    else
	{
	sign=1;
	i=0;
	}
    for (;s[i]!=EOS;i++)
	val=base*val+digval(s[i]);
    return(sign ? val : -val);
    }


static int digval(char c)

    {
    c=tolower((int)c);
    return((int)(c<='9' ? c-'0' : c-'a'+10));
    }


static void sci_out(double val,char *buf)

    {
    double   ex,f;
    int      sf,sn,i,j,iex,dec,nz;

    initbuf(buf);
    if (sigfig<=0)
	sf=maxdigits;
    else
	sf=sigfig;
    if (val==0.)
	{
	incbuf((char)'0');
	if (fixed && sf>1)
	    {
	    incbuf((char)'.');
	    for (i=1;i<sf;i++)
		incbuf((char)'0');
	    }
	addtobuf(obase>14 ? " x 10^0":"e0");
	return;
	}
    sn=val>0;
    val=fabs(val);
    ex=log(val)/log((double)obase);
    ex=floor(ex)+1.;
    iex=ex;
    val=val+(obase/2.)*pow((double)obase,ex-1.-sf);
    f=val/pow((double)obase,ex);
    if (f>=1.)
	{
	f=f/obase;
	iex++;
	}
    initbuf(buf);
    dec=0;
    nz=0;
    if (!sn)
	incbuf((char)'-');
    for (i=0;i<sf;i++)
	{
	if (i==1)
	    dec=1;
	if (i>=maxdigits)
	    nz++;
	else
	    {
	    f=f*obase;
	    if (f>=1.)
		{
		if (dec)
		    {
		    incbuf((char)'.');
		    dec=0;
		    }
		if (nz)
		    {
		    for (j=0;j<nz;j++)
			incbuf((char)'0');
		    nz=0;
		    }
		j=f;
		incbuf((char)(j<10 ? '0'+j : 'A'+j-10));
		f=f-j;
		}
	    else
		nz++;
	    }
	}
    if (fixed)
	{
	if (dec)
	    incbuf((char)'.');
	if (nz)
	    for (j=0;j<nz;j++)
		incbuf((char)'0');
	}
    addtobuf(obase>14 ? " x 10^" : "e");
    intadd(iex-1);
    }


static void intadd(int x)

    {
    char    temp[100];
    int     i,j,sn;

    if (!x)
	{
	incbuf((char)'0');
	return;
	}
    sn=(x>=0);
    if (x<0)
	x=-x;
    for (i=0;x;i++)
	{
	j=x%obase;
	temp[i]=j<10 ? j+'0' : 'A'+j-10;
	x/=obase;
	}
    if (!sn)
	incbuf((char)'-');
    for (j=i-1;j>=0;j--)
	incbuf((char)temp[j]);
    }


static void free_out(double val,char *buf)

    {
    double  ex,f;
    int     sf,sn,i,j,k,iex,iex2,nz,dec;

    initbuf(buf);
    if (fixed || sigfig<=0)
	sf=maxdigits;
    else
	sf=sigfig;
    if (val==0.)
	{
	incbuf((char)'0');
	if (fixed)
	    {
	    if (dplace>0)
		incbuf((char)'.');
	    for (i=0;i<dplace;i++)
		incbuf((char)'0');
	    }
	return;
	}
    sn=val>0;
    val=fabs(val);
    ex=log(val)/log((double)obase);
    ex=floor(ex)+1.;
    if (maxexp>=0 && fabs(ex)>=maxexp+1)
	{
	sci_out(sn ? val : -val,buf);
	return;
	}
    iex=ex;
    if (fixed)
	iex2=-1-dplace;
    else
	iex2=ex-1-sf;
    val=val+(obase/2.)*pow((double)obase,(double)iex2);
    if (iex-maxdigits-1<iex2)
	val=val+pow((double)obase,(double)iex-maxdigits);
    f=val/pow((double)obase,ex);
    if (f>=1.)
	{
	f=f/obase;
	iex++;
	}
    k=iex;
    if (!sn)
	incbuf((char)'-');
    dec=0;
    nz=0;
    if (fixed && iex<=-dplace)
	{
	incbuf((char)'0');
	if (dplace>0)
	    {
	    incbuf((char)'.');
	    for (j=0;j<dplace;j++)
		incbuf((char)'0');
	    }
	return;
	}
    if (iex<=0)
	{
	incbuf((char)'0');
	dec=1;
	nz=-iex;
	}
    for (i=0;fixed || i<sf;i++)
	{
	f=f*obase;
	if (i<maxdigits && f>=1.)
	    {
	    if (dec)
		{
		incbuf((char)'.');
		dec=0;
		}
	    if (nz)
		{
		for (j=0;j<nz;j++)
		    incbuf((char)'0');
		nz=0;
		}
	    j=f;
	    incbuf((char)(j<10 ? '0'+j : 'A'+j-10));
	    f=f-j;
	    }
	else
	    if (k<=0)
		nz++;
	    else
		incbuf((char)'0');
	k--;
	if (fixed && k<=-dplace)
	    break;
	if (k==0)
	    dec=1;
	}
    if (k>0)
	for (;k>0;k--)
	    incbuf((char)'0');
    if (fixed)
	{
	if (dec)
	    incbuf((char)'.');
	if (nz)
	    for (j=0;j<nz;j++)
		incbuf((char)'0');
	}
    }


static void addtobuf(char *s)

    {
    int     i;

    for (i=0;s[i]!=EOS;i++)
	incbuf((char)s[i]);
    }


static int   bufindex;
static char *bufptr;

static void initbuf(char *s)

    {
    bufindex=0;
    bufptr=s;
    }


static void incbuf(char c)

    {
    if (bufindex>MAXOUTLEN-50)
	return;
    if (bufindex==MAXOUTLEN-50)
	{
	bufindex++;
	strcat(bufptr,"*** BUFFER OVERFLOW!");
	return;
	}
    bufptr[bufindex++]=c;
    bufptr[bufindex]=EOS;
    }
