/* ecvt, fcvt -- convert floating-point numbers to ascii	-*- C -*-	*/

/* Copyright (c) 2009 Ian Piumarta
 * 
 * All rights reserved.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the 'Software'), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, and/or sell copies
 * of the Software, and to permit persons to whom the Software is furnished to
 * do so, provided that the above copyright notice(s) and this permission
 * notice appear in all copies of the Software.  Inclusion of the above
 * copyright notice(s) and this permission notice in supporting documentation
 * would be appreciated but is not required.
 *
 * THE SOFTWARE IS PROVIDED 'AS IS'.  USE ENTIRELY AT YOUR OWN RISK.
 */

/* This file provides replacements for the functions ecvt() and fcvt() that
 * were recently deprecated in POSIX.  The interface and behaviour is identical
 * to the functions that they replace (and faster too, at least on my machine).
 * They have been tested on 32- and 64-bit machines of both orientations.
 * 
 * For details on the use of these functions, see your ecvt(3) manual page.  If
 * you don't have one handy, there might still be one available here:
 * http://opengroup.org/onlinepubs/007908799/xsh/ecvt.html
 */

#include <math.h>
#include <stdint.h>
#include <stdlib.h>
#include <assert.h>

static char *convert(double value, int ndigit, int *decpt, int *sign, int fflag)
{
  static char *buf= 0;
  static int   bufsize= 0;
  union { uint64_t l;  double f; } x;
  x.f= value;
  int	   exp2= (0x7ff & (x.l >> 52)) -1023;
  uint64_t mant= x.l & 0x000fffffffffffffULL;
  if ((*sign= x.l >> 63)) value= -value;
  if (exp2 == 0x400) {
    *decpt= 0;
    return mant ? "nan" : "inf";
  }
  int exp10= (value == 0) ? !fflag : (int)ceil(log10(value));
  if (exp10 < -307) exp10= -307;	/* otherwise overflow in pow() */
  value *= pow(10.0, -exp10);
  if (value) {
    while (value <  0.1) { value *= 10;  --exp10; }
    while (value >= 1.0) { value /= 10;  ++exp10; }
  }									assert(value == 0 || (0.1 <= value && value < 1.0));
  if (fflag) {
    if (ndigit + exp10 < 0) {
      *decpt= -ndigit;
      return "";
    }
    ndigit += exp10;
  }
  *decpt= exp10;
  if (bufsize < ndigit + 2) {
    bufsize= ndigit + 2;
    buf= buf ? realloc(buf, bufsize) : malloc(bufsize);
  }
  int ptr= 1;
#if 0	/* slow and safe (and dreadfully boring) */
  while (ptr <= ndigit) {
    double i;
    value= modf(value * 10, &i);
    buf[ptr++]= '0' + (int)i;
  }
  if (value >= 0.5)
    while (--ptr && ++buf[ptr] > '9')
      buf[ptr]= '0';
#else	/* faster */
  x.f= value;
  exp2= (0x7ff & (x.l >> 52)) -1023;			  		assert(value == 0 || (-4 <= exp2 && exp2 <= -1));
  mant= x.l & 0x000fffffffffffffULL;
  if (exp2 == -1023)
    ++exp2;
  else
    mant |= 0x0010000000000000ULL;
  mant <<= (exp2 + 4);			/* 56-bit denormalised signifier */
  while (ptr <= ndigit) {
    mant &= 0x00ffffffffffffffULL;	/* mod 1.0 */
    mant= (mant << 1) + (mant << 3);
    buf[ptr++]= '0' + (mant >> 56);
  }
  if (mant & 0x0080000000000000ULL)	/* 1/2 << 56 */
    while (--ptr && ++buf[ptr] > '9')
      buf[ptr]= '0';
#endif
  if (ptr) {
    buf[ndigit + 1]= 0;
    return buf + 1;
  }
  if (fflag) {
    ++ndigit;
    ++*decpt;
  }
  buf[0]= '1';
  buf[ndigit]= 0;
  return buf;
}

char *e_cvt(double value, int ndigit, int *decpt, int *sign)	{ return convert(value, ndigit, decpt, sign, 0); }
char *f_cvt(double value, int ndigit, int *decpt, int *sign)	{ return convert(value, ndigit, decpt, sign, 1); }

/* ---------------------------------------------------------------- */

#if defined(TEST_FCVT)

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

int mod10cmp16(char *a, char *b)
{
  int i= 0;
  while (a[i] == b[i])
    if (!a[i])	return 0;
    else	++i;
  if      (a[i] == b[i] + 1) { while (b[i + 1] == '9' || b[i + 1] == '.') ++i;  if (b[i + 1] >= '5') ++i; }
  else if (b[i] == a[i] + 1) { while (a[i + 1] == '9' || a[i + 1] == '.') ++i;  if (a[i + 1] >= '5') ++i; }
  if ((i >= 15) || (!a[i + 1] && !b[i + 1])) return 0;
  printf("%d <%s>\n", i, a + i);
  printf("%d <%s>\n", i, b + i);
  return 1;
}

int main(int argc, char **argv)
{
  if (argc > 1) {
    while (argc > 1) {
      --argc;
      ++argv;
      double d= atof(*argv);
      printf("%.3f %.20f\n", d, d);
      int decpt, sign;
      char *cvtbuf;
      cvtbuf=  ecvt(d, 3, &decpt, &sign);		printf("e \t%d\t%d\t<%s>\n", sign, decpt, cvtbuf);
      cvtbuf= e_cvt(d, 3, &decpt, &sign);		printf("e_\t%d\t%d\t<%s>\n", sign, decpt, cvtbuf);
      cvtbuf=  fcvt(d, 3, &decpt, &sign);		printf("f \t%d\t%d\t<%s>\n", sign, decpt, cvtbuf);
      cvtbuf= f_cvt(d, 3, &decpt, &sign);		printf("f_\t%d\t%d\t<%s>\n", sign, decpt, cvtbuf);
    }
  }
  else {
#   define METHOD 1
    srandom(getpid());
    int i;
    for (i= 0;  i < 100000;  ++i) {
      if (i % 1000 == 0) { printf(" %d\r", i); fflush(stdout); }
      union { uint32_t l[2];  double f; } x;
      x.l[0]= random() ^ (random() << 16);
      x.l[1]= random() ^ (random() << 16);
      int ossign, osdecpt, mysign, mydecpt;
      char *osbuf, *mybuf;
      osbuf= ecvt(x.f, 9, &osdecpt, &ossign);
      switch (METHOD) {
	case 0: mybuf=  ecvt(x.f, 9, &mydecpt, &mysign);	break;
	case 1: mybuf= e_cvt(x.f, 9, &mydecpt, &mysign);	break;
      }
      if (ossign != mysign || osdecpt != mydecpt || strcmp(osbuf, mybuf)) {
	printf("ECVT %08x%08x %.10f\n", x.l[0], x.l[1], x.f);
	printf("%d\t%d\t<%s>\n", ossign, osdecpt, osbuf);
	printf("%d\t%d\t<%s>\n", mysign, mydecpt, mybuf);
	printf("fail\n");
	fflush(stdout);
	abort();
      }
      osbuf= fcvt(x.f, 9, &osdecpt, &ossign);
      switch (METHOD) {
	case 0: mybuf=  fcvt(x.f, 9, &mydecpt, &mysign);	break;
	case 1: mybuf= f_cvt(x.f, 9, &mydecpt, &mysign);	break;
      }
      if (ossign != mysign || osdecpt != mydecpt || mod10cmp16(osbuf, mybuf)) {
	printf("FCVT %08x%08x %.10f\n", x.l[0], x.l[1], x.f);
	printf("%d\t%d\t<%s>\n", ossign, osdecpt, osbuf);
	printf("%d\t%d\t<%s>\n", mysign, mydecpt, mybuf);
	printf("fail\n");
	fflush(stdout);
	abort();
      }
    }
    printf(" %d\nok\n", i);
  }

  return 0;
}

#endif /* defined(TEST_FCVT) */

