Skip to content

Instantly share code, notes, and snippets.

@jkbonfield
Created August 16, 2016 15:57
Show Gist options
  • Select an option

  • Save jkbonfield/32702b1b2aa4de7b7e483d6c1156c51a to your computer and use it in GitHub Desktop.

Select an option

Save jkbonfield/32702b1b2aa4de7b7e483d6c1156c51a to your computer and use it in GitHub Desktop.
printf %g equivalent with explicit force to floating point type.
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <math.h>
typedef struct __kstring_t {
size_t l, m;
char *s;
} kstring_t;
#ifndef kroundup32
#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x))
#endif
#ifndef kroundup_size_t
#define kroundup_size_t(x) (--(x), \
(x)|=(x)>>(sizeof(size_t)/8), /* 0 or 1 */ \
(x)|=(x)>>(sizeof(size_t)/4), /* 1 or 2 */ \
(x)|=(x)>>(sizeof(size_t)/2), /* 2 or 4 */ \
(x)|=(x)>>(sizeof(size_t)), /* 4 or 8 */ \
(x)|=(x)>>(sizeof(size_t)*2), /* 8 or 16 */ \
(x)|=(x)>>(sizeof(size_t)*4), /* 16 or 32 */ \
++(x))
#endif
static inline int ks_resize(kstring_t *s, size_t size)
{
if (s->m < size) {
char *tmp;
kroundup_size_t(size);
tmp = (char*)realloc(s->s, size);
if (!tmp)
return -1;
s->s = tmp;
s->m = size;
}
return 0;
}
static inline int kputsn(const char *p, int l, kstring_t *s)
{
if (ks_resize(s, s->l + l + 2) < 0)
return EOF;
memcpy(s->s + s->l, p, l);
s->l += l;
s->s[s->l] = 0;
return l;
}
static inline int kputc(int c, kstring_t *s)
{
if (ks_resize(s, s->l + 2) < 0)
return EOF;
s->s[s->l++] = c;
s->s[s->l] = 0;
return c;
}
int ksprintf_g(kstring_t *s, double d) {
int len = 0;
char buf[21], *cp = buf+20, *ep;
if (d == 0) {
if (signbit(d)) {
kputsn("-0.0",4,s);
return 2;
} else {
kputsn("0.0",3,s);
return 1;
}
}
if (d < 0) {
kputc('-',s);
d=-d;
}
if (d < 0.0001 || d > 999999) {
if (ks_resize(s, s->l + 50) < 0)
return EOF;
// We let stdio handle the exponent cases
len = sprintf(s->s + s->l, "%g", d);
s->l += len;
return len;
}
uint64_t i = d*10000000000;
// Correction for rounding - rather ugly
// Optimised for small numbers.
// Better still would be __builtin_clz on hi/lo 32 and get the
// starting point very rapidly.
if (d<.0001)
i+=0;
else if (d<0.001)
i+=5;
else if (d < 0.01)
i+=50;
else if (d < 0.1)
i+=500;
else if (d < 1)
i+=5000;
else if (d < 10)
i+=50000;
else if (d < 100)
i+=500000;
else if (d < 1000)
i+=5000000;
else if (d < 10000)
i+=50000000;
else if (d < 100000)
i+=500000000;
else
i+=5000000000;
do {
*--cp = '0' + i%10;
i /= 10;
} while (i >= 1);
buf[20] = 0;
int p = buf+20-cp;
if (p <= 10) { // d < 1
//assert(d/1);
cp[6] = 0; ep = cp+5;// 6 precision
while (p < 10) {
*--cp = '0';
p++;
}
*--cp = '.';
*--cp = '0';
} else {
char *xp = --cp;
while (p > 10) {
xp[0] = xp[1];
p--;
xp++;
}
xp[0] = '.';
cp[7] = 0; ep=cp+6;
if (cp[6] == '.') cp[6] = 0;
}
// Cull trailing zeros
while (*ep == '0' && ep > cp)
ep--;
char *z = ep+1;
while (ep > cp) {
if (*ep == '.') {
if (z[-1] == '.') {
// small whole number, add explicit "."
z[0] = 0;
} else {
z[0] = 0;
}
break;
}
ep--;
}
if (ep == cp) {
// 6 digit whole number or truncated to 6 digits.
// Misleading as "123456.7" becomes "123457." implying
// 123457.something and >= 123457. However it forces
// the number to be interpreted as floating point by buggy
// software.
cp[6] = '.';
cp[7] = 0;
}
len = strlen(cp);
kputsn(cp, len, s);
return len;
}
int main(int argc, char **argv) {
char buf1[100], buf2[100];
kstring_t s = {0, 100, buf1};
double d = atof(argv[1]);
ksprintf_g(&s, d);
sprintf(buf2, "%g", d);
printf("libc ='%s'\n"
"kstring='%s'\n",
buf2, buf1);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment