==> 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}