Snippets

Takehiko NOZAKI gdtoa usage (printf %f 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 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
cvt_fsize(int sign, int len, int prec, int exp, int flags)
{
	int s, i, f, d;

	s = (sign) ? 1 : 0;
	i = (exp <= 0) ? 1 : exp;
	f = (exp > len) ? 0 : len - exp;
	if (f < prec)
		f = prec;
	d = (f > 0 || flags & SHARP) ? 1 : 0;
	return s + i + f + d;
}

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

	sign = cvt_sign(flags);
	size = cvt_fsize(sign, len, prec, exp, flags);
	if (lpad(sign, width, size, flags, fp))
		return 1;
	if (len <= exp) {
		if (fwrite(head, 1, len, fp) != len)
			return 1;
		if (pad(zeros, exp - len, fp))
			return 1;
		if (((flags & SHARP) || prec > 0) && putc('.', fp) == EOF)
			return 1;
		len = 0;
	} else if (exp > 0) {
		if (fwrite(head, 1, exp, fp) != exp)
			return 1;
 		if (putc('.', fp) == EOF)
			return 1;
		len -= exp;
		if (fwrite(&head[exp], 1, len, fp) != len)
			return 1;
	} else {
 		if (fwrite("0.", 1, 2, fp) != 2)
			return 1;
		exp = -exp;
		if (exp > 0 && pad(zeros, exp, fp))
			return 1;
		if (fwrite(head, 1, len, fp) != len)
			return 1;
		len += exp;
	}
	if (prec > len && pad(zeros, prec - len, fp))
		return 1;
	if (rpad(width, size, flags, fp))
		return 1;
	return 0;
}

int
fcvt(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, 3, prec, &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_ffmt(head, len, width, prec, exp, 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(12.0),
		DTOATEST(123.0),
		DTOATEST(10.0),
		DTOATEST(100.0),
		DTOATEST(1000.0),
		DTOATEST(0.1),
		DTOATEST(0.01),
		DTOATEST(0.001),
		DTOATEST(1.1),
		DTOATEST(1.12),
		DTOATEST(1.123),
		DTOATEST(12.1),
		DTOATEST(12.12),
		DTOATEST(12.123),
		DTOATEST(123.1),
		DTOATEST(123.12),
		DTOATEST(123.123),
		DTOATEST(0.123456789000),
		DTOATEST(0.0123456789000),
		DTOATEST(0.00123456789000),
		DTOATEST(0.0001234567890000),
	};
	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.*f", prec, t[i].dvalue) < 0)
				abort();
			result = NULL;
			n = 0;
			fp = open_memstream(&result, &n);
			if (fp == NULL)
				abort();
			if (fcvt(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.