Snippets

Takehiko NOZAKI gdtoa usage (printf %e conversion like)

Created by Takehiko NOZAKI last modified
/*-
 * Copyright (c) 2021 Takehiko NOZAKI
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#define _GNU_SOURCE /* for asprintf(3) */
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#if defined(HAVE_MATH_H)
#include <math.h>
#endif

#if defined(__NetBSD__)
extern char *__dtoa(double, int, int, int *, int *, char **);
extern void __freedtoa(char *);
#define dtoa		__dtoa
#define freedtoa	__freedtoa
#include <sys/cdefs.h>
#define arraycount(array)	__arraycount(array)
#else
#include "gdtoa.h"
#define arraycount(array)       (sizeof(array)/sizeof(array[0]))
#endif

struct dtoatestcase {
	double dvalue;
	const char *svalue;
};

#define DTOATEST(value) { .dvalue = value, .svalue = #value }
#define debug	printf

#define DEFAULTPREC	6
#define MAXEXPSIZ	4

#define NEGATIVE	0x1
#define SQUOTE		0x2
#define MINUS		0x4
#define PLUS		0x8
#define SPACE		0x10
#define SHARP		0x20
#define ZERO		0x40
#define HEX			0x80

#define PADSIZ	20
static const char zeros[PADSIZ] = "00000000000000000000";
static const char spaces[PADSIZ] = "                    ";

static inline int
pad(const char *filler, int len, FILE *fp)
{
	while (len > PADSIZ) {
		if (fwrite(filler, 1, PADSIZ, fp) != PADSIZ)
			return 1;
		len -= PADSIZ;
	}
	if (fwrite(filler, 1, len, fp) != len)
		return 1;
	return 0;
}

static inline int
lpad(int sign, int width, int siz, int flags, FILE *fp)
{
	if (width > siz && (flags & (MINUS|ZERO)) == 0 &&
	    pad(spaces, width - siz, fp))
		return 1;
	if (sign && putc(sign, fp) == EOF)
		return 1;
	if ((flags & HEX) && fwrite("0x", 1, 2, fp) != 2)
		return 1;
	if (width > siz && (flags & (MINUS|ZERO)) == ZERO &&
	    pad(zeros, width - siz, fp))
		return 1;
	return 0;
}

static inline int
rpad(int width, int siz, int flags, FILE *fp)
{
	if (width > siz && (flags & MINUS) &&
	    pad(spaces, width - siz, fp))
		return 1;
	return 0;
}

static inline int
cvt_sign(int flags)
{
	if (flags & NEGATIVE)
		return '-';
	else if (flags & PLUS)
		return '+';
	else if (flags & SPACE)
		return ' ';
	return '\0';
}

static inline int
cvt_inf(int width, int flags, FILE *fp)
{
	int sign, siz;

	sign = cvt_sign(flags);
	siz = (sign) ? 4 : 3;
	if (lpad(sign, width, siz, flags & ~ZERO, fp))
		return 1;
	if (fwrite("inf", 1, 3, fp) != 3)
		return 1;
	if (rpad(width, siz, flags, fp))
		return 1;
	return 0;
}

static inline int
cvt_nan(int width, int flags, FILE *fp)
{
	if (lpad('\0', width, 3, flags & ~ZERO, fp))
		return 1;
	if (fwrite("nan", 1, 3, fp) != 3)
		return 1;
	if (rpad(width, 3, flags, fp))
		return 1;
	return 0;
}

static inline int
to_char(int digit)
{
	return '0' + digit;
}

static inline int
cvt_exp(int exp, FILE *fp)
{
	char expstr[MAXEXPSIZ + 2];
	int sign;
	size_t len;

	expstr[0] = 'e';
	if (exp >= 0) {
		expstr[1] = '+';
	} else {
		expstr[1] = '-';
		exp = -exp;
	}
	/* assume DBL_MIN_EXP(-1021) DBL_MAX_EXP(1024) */
	if (exp < 100) {
		expstr[2] = to_char(exp / 10);
		expstr[3] = to_char(exp % 10);
		len = 4;
	} else if (exp < 1000) {
		expstr[2] = to_char(exp / 100);
		expstr[3] = to_char((exp % 100) / 10);
		expstr[4] = to_char(exp % 10);
		len = 5;
	} else {
		expstr[2] = to_char(exp / 1000);
		expstr[3] = to_char((exp % 1000) / 100);
		expstr[4] = to_char((exp % 100) / 10);
		expstr[5] = to_char(exp % 10);
		len = 6;
	}
	if (fwrite(expstr, 1, len, fp) != len)
		return 1;
	return 0;
}

static inline int
cvt_esize(int sign, int len, int prec, int exp, int flags)
{
	int s, i, f, d, e;

	s = (sign) ? 1 : 0;
	i = 1;
	f = len - 1;
	if (f < prec)
		f = prec;
	d = (f > 0 || flags & SHARP) ? 1 : 0;
	if (exp < 0)
		exp = -exp;
	/* assume DBL_MIN_EXP(-1021) DBL_MAX_EXP(1024) */
	if (exp < 100)
		e = 4;
	else if (exp < 1000)
		e = 5;
	else
		e = 6;
	return s + i + f + d + e;
}

static inline int
cvt_efmt(char *head, int len, int width, int prec, int exp, int flags, FILE *fp)
{
	int sign, size;

	sign = cvt_sign(flags);
	size = cvt_esize(sign, len, prec, exp, flags);
	if (lpad(sign, width, size, flags, fp))
		return 1;
	if (putc(*head, fp) == EOF)
		return 1;
	if (((flags & SHARP) || prec > 0) && putc('.', fp) == EOF)
		return 1;
	if (--len > 0 && fwrite(&head[1], 1, len, fp) != len)
		return 1;
	if (prec > len && pad(zeros, prec - len, fp))
		return 1;
	if (cvt_exp(exp, fp))
		return 1;
	if (rpad(width, size, flags, fp))
		return 1;
	return 0;
}

int
ecvt(double dvalue, int width, int prec, FILE *fp)
{
	int flags, exp, neg, len, ret;
	char *head, *tail;

	flags = 0;
	if (prec < 0)
		prec = DEFAULTPREC;
	head = dtoa(dvalue, 2, prec + 1, &exp, &neg, &tail);
	if (head == NULL)
		return 1;
	len = tail - head;
	if (neg)
		flags |= NEGATIVE;
	if (exp == 9999) {
		switch (*head) {
		case 'I':
			ret = cvt_inf(width, flags, fp);
			break;
		case 'N':
			ret = cvt_nan(width, flags, fp);
			break;
		default:
			ret = 1;
		}
	} else {
		ret = cvt_efmt(head, len, width, prec, exp - 1, flags, fp);
	}
	freedtoa(head);
	return ret;
}

int
main(int argc, char *argv[])
{
	static const struct dtoatestcase t[] = {
#if defined(HAVE_MATH_H) && !defined(__vax__)
		DTOATEST(INF),
		DTOATEST(NAN),
#else
		DTOATEST(1.0/0.0),
		DTOATEST(0.0/0.0),
#endif
		DTOATEST(0.0),
		DTOATEST(1.0),
		DTOATEST(10.0),
		DTOATEST(100.0),
		DTOATEST(1.1),
		DTOATEST(1.123456789),
		DTOATEST(11.23456789),
		DTOATEST(112.3456789),
		DTOATEST(1123.456789),
		DTOATEST(11234.56789),
		DTOATEST(112345.6789),
		DTOATEST(1123456.789),
		DTOATEST(11234567.89),
		DTOATEST(112345678.9),
		DTOATEST(1123456789.0),
		DTOATEST(11234567890123.0),
	};
	size_t i, n;
	int prec;
	FILE *fp;
	char *expect, *result;

	for (i = 0; i < arraycount(t); ++i) {
		for (prec = -1; prec <= 20; ++prec) {
			expect = NULL;
			if (asprintf(&expect, "%30.*e", prec, t[i].dvalue) < 0)
				abort();
			result = NULL;
			fp = open_memstream(&result, &n);
			if (fp == NULL)
				abort();
			if (ecvt(t[i].dvalue, 30, prec, fp))
				abort();
			fclose(fp);
			printf("testcase:[%s], expect:[%s], result[%s]\n",
				t[i].svalue, expect, result);
			if (strcmp(result, expect))
				abort();
			free(expect);
			free(result);
		}
	}
	exit(EXIT_SUCCESS);
}

Comments (0)

HTTPS SSH

You can clone a snippet to your computer for local editing. Learn more.