skip to content
[readonly]
==> src/rust_fmt.c <==
1/* Rust's float Display and string Debug, in C (see the header for why printf is not enough). */
2#include "mesh/rust_fmt.h"
3
4#include <math.h>
5#include <stdio.h>
6#include <stdlib.h>
7#include <string.h>
8
9/* The exact decimal expansion of x > 0 (every binary float has a finite one): its digits without
10 * the point (no trailing zeros) and the decimal exponent of the first. */
11static void exact(double x, char *digits, size_t cap, int *exp10) {
12    char buf[1100];
13    snprintf(buf, sizeof buf, "%.*e", 1000, x);
14    size_t n = 0;
15    const char *c = buf;
16    for (; *c && *c != 'e' && n + 1 < cap; c++)
17        if (*c >= '0' && *c <= '9') digits[n++] = *c;
18    while (n > 1 && digits[n - 1] == '0') n--;
19    digits[n] = 0;
20    while (*c && *c != 'e') c++;
21    *exp10 = atoi(c + 1);
22}
23
24/* The first p digits of the exact digits, rounded to nearest with ties away from zero (as Rust's
25 * shortest Display picks between two equally close candidates): into out, the exponent adjusted on a
26 * carry. */
27static void round_half_up(const char *ex, int e, int p, char *out, int *exp10) {
28    int n = (int)strlen(ex);
29    for (int i = 0; i < p; i++) out[i] = i < n ? ex[i] : '0';
30    out[p] = 0;
31    *exp10 = e;
32    if (n <= p || ex[p] < '5') return;
33    for (int i = p - 1; i >= 0; i--) {
34        if (out[i] < '9') {
35            out[i]++;
36            return;
37        }
38        out[i] = '0';
39    }
40    /* 99..9 rounded up: 100..0 one decade higher. */
41    memmove(out + 1, out, (size_t)p);
42    out[0] = '1';
43    out[p] = 0;
44    *exp10 = e + 1;
45}
46
47/* The shortest digits of x > 0 that read back as x (f32 when is_f32): the digits without the point
48 * and the decimal exponent of the first. */
49static void shortest(double x, int max_digits, int is_f32, char digits[32], int *exp10) {
50    char ex[1100];
51    int e;
52    exact(x, ex, sizeof ex, &e);
53    for (int p = 1; p <= max_digits; p++) {
54        int ee;
55        round_half_up(ex, e, p, digits, &ee);
56        char buf[64];
57        snprintf(buf, sizeof buf, "%c.%se%d", digits[0], digits + 1, ee);
58        if (p == 1) snprintf(buf, sizeof buf, "%ce%d", digits[0], ee);
59        int same = is_f32 ? (strtof(buf, NULL) == (float)x) : (strtod(buf, NULL) == x);
60        if (same || p == max_digits) {
61            *exp10 = ee;
62            break;
63        }
64    }
65    size_t n = strlen(digits);
66    while (n > 1 && digits[n - 1] == '0') digits[--n] = 0;
67}
68
69/* Digits d1 d2 ... dn times 10^(exp10 - n + 1), written out without an exponent. */
70static char *plain(char *out, int neg, const char *digits, int exp10) {
71    char *o = out;
72    if (neg) *o++ = '-';
73    int n = (int)strlen(digits);
74    int point = exp10 + 1; /* digits before the decimal point */
75    if (point <= 0) {
76        *o++ = '0';
77        *o++ = '.';
78        for (int i = 0; i < -point; i++) *o++ = '0';
79        memcpy(o, digits, (size_t)n);
80        o += n;
81    } else if (point >= n) {
82        memcpy(o, digits, (size_t)n);
83        o += n;
84        for (int i = 0; i < point - n; i++) *o++ = '0';
85    } else {
86        memcpy(o, digits, (size_t)point);
87        o += point;
88        *o++ = '.';
89        memcpy(o, digits + point, (size_t)(n - point));
90        o += n - point;
91    }
92    *o = 0;
93    return out;
94}
95
96static char *display(char *out, double x, int max_digits, int is_f32) {
97    if (isnan(x)) return strcpy(out, "NaN");
98    if (isinf(x)) return strcpy(out, x < 0 ? "-inf" : "inf");
99    if (x == 0) return strcpy(out, signbit(x) ? "-0" : "0");
100    char digits[32];
101    int e;
102    shortest(fabs(x), max_digits, is_f32, digits, &e);
103    return plain(out, x < 0, digits, e);
104}
105
106char *rf_f32(char out[RF_FLOAT_MAX], float x) { return display(out, (double)x, 9, 1); }
107char *rf_f64(char out[RF_FLOAT_MAX], double x) { return display(out, x, 17, 0); }
108
109char *rf_debug_str(const char *s) {
110    size_t n = strlen(s);
111    char *out = malloc(n * 10 + 3);
112    if (!out) return NULL;
113    char *o = out;
114    *o++ = '"';
115    for (const unsigned char *c = (const unsigned char *)s; *c; c++) {
116        switch (*c) {
117        case '\t': *o++ = '\\'; *o++ = 't'; break;
118        case '\r': *o++ = '\\'; *o++ = 'r'; break;
119        case '\n': *o++ = '\\'; *o++ = 'n'; break;
120        case '\\': *o++ = '\\'; *o++ = '\\'; break;
121        case '"': *o++ = '\\'; *o++ = '"'; break;
122        case 0: *o++ = '\\'; *o++ = '0'; break;
123        default:
124            if (*c < 0x20 || *c == 0x7f) o += sprintf(o, "\\u{%x}", *c);
125            else *o++ = (char)*c;
126        }
127    }
128    *o++ = '"';
129    *o = 0;
130    return out;
131}
132
133char *rf_hex_alt(char out[24], uint64_t x) {
134    snprintf(out, 24, "0x%llx", (unsigned long long)x);
135    return out;
136}