Files
vlibc/src/stdio/vfprintf_float.c
T

843 lines
17 KiB
C

/* internal unsigned 2^32-limb bignum core for printf float formatting (todo 16)
* clean-room implementation; nothing here is exported */
/*
* Invariants: limbs are little-endian (value = sum d[i] * 2^(32*i)); n == 0
* means value 0; no trailing-zero-limb invariant is kept -- callers normalize
* via vfpn_bn_norm. Shifts and rounding operate in place; allocation failure
* is reported as -1 and leaves errno to __libc_malloc.
*/
#include "../internal/malloc.h"
#include <stddef.h>
#include <stdint.h>
struct vfpn_bn
{
uint32_t fixed[640];
uint32_t *d;
size_t n;
size_t cap;
int heap;
};
static __attribute__((unused)) void
vfpn_bn_init(struct vfpn_bn *b)
{
b->d = b->fixed;
b->n = 0;
b->cap = 640;
b->heap = 0;
}
static __attribute__((unused)) void
vfpn_bn_free(struct vfpn_bn *b)
{
if (b->heap)
{
__libc_free(b->d);
}
}
static __attribute__((unused)) int
vfpn_bn_reserve(struct vfpn_bn *b, size_t need)
{
if (need <= b->cap)
{
return 0;
}
uint32_t *new = __libc_malloc(need * sizeof(uint32_t));
if (!new)
{
return -1;
}
size_t copy = (b->cap < need) ? b->cap : need;
__builtin_memcpy(new, b->d, copy * sizeof(uint32_t));
if (b->heap)
{
__libc_free(b->d);
}
b->d = new;
b->cap = need;
b->heap = 1;
return 0;
}
static __attribute__((unused)) void
vfpn_bn_norm(struct vfpn_bn *b)
{
while (b->n > 0 && b->d[b->n - 1] == 0)
{
b->n--;
}
}
static __attribute__((unused)) int
vfpn_bn_set_u64(struct vfpn_bn *b, uint64_t v)
{
size_t need = 0;
for (uint64_t t = v; t != 0; t >>= 32)
{
need++;
}
if (vfpn_bn_reserve(b, need) != 0)
{
return -1;
}
b->n = 0;
while (v != 0)
{
b->d[b->n++] = (uint32_t)v;
v >>= 32;
}
return 0;
}
static __attribute__((unused)) int
vfpn_bn_mul_small(struct vfpn_bn *b, uint32_t m)
{
if (m == 0)
{
b->n = 0;
return 0;
}
if (vfpn_bn_reserve(b, b->n + 1) != 0)
{
return -1;
}
uint64_t carry = 0;
for (size_t i = 0; i < b->n; i++)
{
uint64_t cur = (uint64_t)b->d[i] * m + carry;
b->d[i] = (uint32_t)cur;
carry = cur >> 32;
}
if (carry != 0)
{
b->d[b->n++] = (uint32_t)carry;
}
return 0;
}
static __attribute__((unused)) int
vfpn_bn_shl(struct vfpn_bn *b, size_t bits)
{
if (bits == 0)
{
return 0;
}
size_t wl = bits / 32;
size_t s = bits % 32;
size_t newn = b->n + wl + ((s != 0) ? 1 : 0);
if (vfpn_bn_reserve(b, newn) != 0)
{
return -1;
}
if (s == 0)
{
for (size_t i = b->n; i-- > 0;)
{
b->d[i + wl] = b->d[i];
}
for (size_t i = 0; i < wl; i++)
{
b->d[i] = 0;
}
}
else
{
for (size_t j = newn; j-- > 0;)
{
uint64_t cur = 0;
if (j >= wl && j - wl < b->n)
{
cur |= (uint64_t)b->d[j - wl] << s;
}
if (j > wl && j - wl - 1 < b->n)
{
cur |= (uint64_t)b->d[j - wl - 1] >> (32 - s);
}
b->d[j] = (uint32_t)cur;
}
}
b->n = newn;
return 0;
}
static __attribute__((unused)) int
vfpn_bn_add_one(struct vfpn_bn *b)
{
size_t i = 0;
while (i < b->n)
{
b->d[i]++;
if (b->d[i] != 0)
{
return 0;
}
i++;
}
if (vfpn_bn_reserve(b, b->n + 1) != 0)
{
return -1;
}
b->d[b->n] = 1;
b->n++;
return 0;
}
static __attribute__((unused)) int
vfpn_bn_is_zero(const struct vfpn_bn *b)
{
return b->n == 0;
}
static __attribute__((unused)) int
vfpn_bn_cmp(const struct vfpn_bn *a, const struct vfpn_bn *b)
{
if (a->n < b->n)
{
return -1;
}
if (a->n > b->n)
{
return 1;
}
for (size_t i = a->n; i-- > 0;)
{
if (a->d[i] < b->d[i])
{
return -1;
}
if (a->d[i] > b->d[i])
{
return 1;
}
}
return 0;
}
static __attribute__((unused)) int
vfpn_bn_shr_round(struct vfpn_bn *b, size_t bits)
{
if (bits == 0)
{
return 0;
}
size_t w = bits / 32;
size_t s = bits % 32;
if (w >= b->n)
{
/* Whole value discarded: result is 0, rounded up iff the original
* value exceeds 2^(bits-1). That can only happen when bits == 32*n
* (s == 0 and w == n), so compare the top limb against half. */
int round = 0;
if (s == 0 && w == b->n && b->n > 0)
{
uint32_t top = b->d[b->n - 1];
if (top > 0x80000000u)
{
round = 1;
}
else if (top == 0x80000000u)
{
for (size_t i = b->n - 1; i-- > 0;)
{
if (b->d[i] != 0)
{
round = 1;
break;
}
}
}
}
b->n = 0;
if (round)
{
return vfpn_bn_add_one(b);
}
return 0;
}
/* Round bit and sticky are captured from the discarded low `bits` bits
* of the original value before the in-place shift. */
int round_bit;
int sticky;
if (s > 0)
{
round_bit = (int)((b->d[w] >> (s - 1)) & 1u);
sticky = (b->d[w] & ((1u << (s - 1)) - 1u)) != 0;
for (size_t i = 0; i < w; i++)
{
if (b->d[i] != 0)
{
sticky = 1;
break;
}
}
}
else
{
round_bit = (int)((b->d[w - 1] >> 31) & 1u);
sticky = (b->d[w - 1] & 0x7FFFFFFFu) != 0;
for (size_t i = 0; i + 1 < w; i++)
{
if (b->d[i] != 0)
{
sticky = 1;
break;
}
}
}
/* In-place right shift by `bits`; ascending so every source limb is
* still readable when its destination is written. */
size_t old_n = b->n;
if (s == 0)
{
for (size_t j = 0; j + w < old_n; j++)
{
b->d[j] = b->d[j + w];
}
}
else
{
for (size_t j = 0; j + w < old_n; j++)
{
uint32_t lo = b->d[j + w] >> s;
uint32_t hi = (j + w + 1 < old_n) ? (b->d[j + w + 1] << (32 - s)) : 0;
b->d[j] = lo | hi;
}
}
b->n = old_n - w;
vfpn_bn_norm(b);
int odd = (b->n > 0 && (b->d[0] & 1u)) ? 1 : 0;
if (round_bit && (sticky || odd))
{
return vfpn_bn_add_one(b);
}
return 0;
}
static __attribute__((unused)) uint32_t
vfpn_bn_divmod_small_1e9(struct vfpn_bn *b)
{
uint64_t r = 0;
for (size_t i = b->n; i-- > 0;)
{
uint64_t cur = (r << 32) | b->d[i];
b->d[i] = (uint32_t)(cur / 1000000000ULL);
r = cur % 1000000000ULL;
}
vfpn_bn_norm(b);
return (uint32_t)r;
}
static __attribute__((unused)) int
vfpn_bn_pow5(struct vfpn_bn *b, size_t k)
{
int r = vfpn_bn_set_u64(b, 1);
if (r != 0)
{
return r;
}
for (size_t i = 0; i < k; i++)
{
r = vfpn_bn_mul_small(b, 5);
if (r != 0)
{
return r;
}
}
return 0;
}
/* --- exact decimal rounding layer ------------------------------------ */
/*
* Number of bits needed to hold |b| (index of the highest set bit plus one);
* 0 for the value zero. The caller may leave a trailing zero limb in place,
* so the scan starts from the recorded length and skips down.
*/
static __attribute__((unused)) size_t
vfpn_bn_bitlen(const struct vfpn_bn *b)
{
if (b->n == 0)
{
return 0;
}
size_t i = b->n - 1;
while (i > 0 && b->d[i] == 0)
{
i--;
}
if (b->d[i] == 0)
{
return 0;
}
return i * 32 + (size_t)(32 - __builtin_clz(b->d[i]));
}
static __attribute__((unused)) int
vfpn_bn_get_bit(const struct vfpn_bn *b, size_t i)
{
return (int)((b->d[i / 32] >> (i % 32)) & 1u);
}
/*
* Set bit i of b. Zero-extends the limb array when the bit lies beyond the
* current length; ORing never clears, so the value only grows.
*/
static __attribute__((unused)) int
vfpn_bn_set_bit(struct vfpn_bn *b, size_t i, int bit)
{
if (!bit)
{
return 0;
}
if (b->n <= i / 32)
{
size_t need = i / 32 + 1;
if (vfpn_bn_reserve(b, need) != 0)
{
return -1;
}
for (size_t j = b->n; j < need; j++)
{
b->d[j] = 0;
}
b->n = need;
}
b->d[i / 32] |= (1u << (i % 32));
return 0;
}
/*
* Shift |b| left by one bit in place (limb ripple). The caller's cap must
* already make room for the possible extra top limb; the reserve here is
* only a safety net and normally returns immediately.
*/
static __attribute__((unused)) int
vfpn_bn_shl1(struct vfpn_bn *b)
{
if (b->n == 0)
{
return 0;
}
if (vfpn_bn_reserve(b, b->n + 1) != 0)
{
return -1;
}
uint32_t carry = 0;
for (size_t i = 0; i < b->n; i++)
{
uint32_t cur = b->d[i];
b->d[i] = (cur << 1) | carry;
carry = cur >> 31;
}
if (carry != 0)
{
b->d[b->n++] = carry;
}
return 0;
}
/*
* In-place limb-wise subtraction a -= b. The caller guarantees a >= b, so
* the borrow always clears before the top of a; a is renormalized.
*/
static __attribute__((unused)) void
vfpn_bn_sub_inplace(struct vfpn_bn *a, const struct vfpn_bn *b)
{
uint32_t borrow = 0;
size_t i = 0;
for (; i < b->n; i++)
{
uint64_t diff = (uint64_t)a->d[i] - (uint64_t)b->d[i] - borrow;
a->d[i] = (uint32_t)diff;
borrow = (uint32_t)((diff >> 32) & 1u);
}
while (i < a->n && borrow != 0)
{
a->d[i]--;
borrow = (a->d[i] == 0xFFFFFFFFu) ? 1u : 0u;
i++;
}
vfpn_bn_norm(a);
}
/*
* Q = N / D, R = N mod D by restoring binary long division. N may be zero;
* D must be nonzero. Q and R are written over whatever they held. The
* quotients bits are produced most-significant first; the loop runs over the
* dividend's bit positions from bitlen(N)-1 down to 0, pulling one new
* dividend bit into R and subtracting D whenever R >= D.
*/
static __attribute__((unused)) int
vfpn_divmod_binary(struct vfpn_bn *Q, struct vfpn_bn *R, const struct vfpn_bn *N,
const struct vfpn_bn *D)
{
if (vfpn_bn_is_zero(D))
{
return -1;
}
if (vfpn_bn_reserve(Q, N->n + 2) != 0)
{
return -1;
}
if (vfpn_bn_reserve(R, N->n + 2) != 0)
{
return -1;
}
Q->n = 0;
R->n = 0;
size_t nbits = vfpn_bn_bitlen(N);
for (size_t i = nbits; i-- > 0;)
{
if (vfpn_bn_shl1(R) != 0)
{
return -1;
}
if (vfpn_bn_set_bit(R, 0, vfpn_bn_get_bit(N, i)) != 0)
{
return -1;
}
if (vfpn_bn_cmp(R, D) >= 0)
{
vfpn_bn_sub_inplace(R, D);
if (vfpn_bn_set_bit(Q, i, 1) != 0)
{
return -1;
}
}
}
vfpn_bn_norm(Q);
vfpn_bn_norm(R);
return 0;
}
/*
* Compare 2*a against b (both canonical base-2^32 digit strings) without
* building the doubled value: doubling a limb's carry is the next limb's
* (d[i] >> 31), which the loop below folds in, and an extra top digit
* appears when the original top limb had its high bit set. All digits of 2*a
* stay below 2^32, so a plain digitwise comparison is exact.
*/
static __attribute__((unused)) int
vfpn_bn_cmp_doubled(const struct vfpn_bn *a, const struct vfpn_bn *b)
{
if (a->n == 0)
{
return vfpn_bn_is_zero(b) ? 0 : -1;
}
size_t extra = (a->d[a->n - 1] >> 31) ? 1u : 0u;
size_t la = a->n + extra;
if (la != b->n)
{
return (la > b->n) ? 1 : -1;
}
for (size_t i = la; i-- > 0;)
{
uint32_t da;
if (extra != 0 && i == a->n)
{
da = 1; /* carry out of the doubled top limb */
}
else
{
da = (uint32_t)(a->d[i] << 1);
if (i > 0)
{
da += (a->d[i - 1] >> 31);
}
}
if (da != b->d[i])
{
return (da < b->d[i]) ? -1 : 1;
}
}
return 0;
}
/*
* R = round_half_even(mant * 2^exp2 * 10^s), computed exactly as an integer
* bignum (mant > 0; s may be negative). s >= 0 scales R by 5^s and by a
* single power of two, rounding once at the end (nearest-even). s < 0 builds
* D = 5^(-s) and shifts either N or D so that value = N / D, divides exactly,
* and rounds the quotient half-even off the remainder: 2*rem > D rounds up,
* 2*rem == D rounds up only when the quotient is odd. Returns 0 or -1 on
* allocation failure (R is then unspecified).
*/
static __attribute__((unused)) int
vfpn_round_scale10(struct vfpn_bn *R, uint64_t mant, int exp2, long s)
{
struct vfpn_bn N;
struct vfpn_bn D;
struct vfpn_bn Q;
struct vfpn_bn rem;
vfpn_bn_init(&N);
vfpn_bn_init(&D);
vfpn_bn_init(&Q);
vfpn_bn_init(&rem);
if (s >= 0)
{
if (vfpn_bn_set_u64(R, mant) != 0)
{
goto fail;
}
for (long i = 0; i < s; i++)
{
if (vfpn_bn_mul_small(R, 5) != 0)
{
goto fail;
}
}
long long sh = (long long)exp2 + (long long)s;
if (sh >= 0)
{
if (vfpn_bn_shl(R, (size_t)sh) != 0)
{
goto fail;
}
}
else
{
if (vfpn_bn_shr_round(R, (size_t)(-sh)) != 0)
{
goto fail;
}
}
vfpn_bn_free(&N);
vfpn_bn_free(&D);
vfpn_bn_free(&Q);
vfpn_bn_free(&rem);
return 0;
}
/* value = mant * 2^exp2 / (2^t * 5^t) with t = -s: make it N / D. */
{
long long t = -(long long)s;
long long sh = (long long)exp2 - t;
if (vfpn_bn_pow5(&D, (size_t)t) != 0)
{
goto fail;
}
if (vfpn_bn_set_u64(&N, mant) != 0)
{
goto fail;
}
if (sh >= 0)
{
if (vfpn_bn_shl(&N, (size_t)sh) != 0)
{
goto fail;
}
vfpn_bn_norm(&N);
}
else
{
if (vfpn_bn_shl(&D, (size_t)(-sh)) != 0)
{
goto fail;
}
vfpn_bn_norm(&D);
}
if (vfpn_divmod_binary(&Q, &rem, &N, &D) != 0)
{
goto fail;
}
int c = vfpn_bn_cmp_doubled(&rem, &D);
if (c > 0 || (c == 0 && Q.n > 0 && (Q.d[0] & 1u) != 0))
{
if (vfpn_bn_add_one(&Q) != 0)
{
goto fail;
}
}
if (vfpn_bn_reserve(R, Q.n) != 0)
{
goto fail;
}
if (Q.n > 0)
{
__builtin_memcpy(R->d, Q.d, Q.n * sizeof(uint32_t));
}
R->n = Q.n;
}
vfpn_bn_free(&N);
vfpn_bn_free(&D);
vfpn_bn_free(&Q);
vfpn_bn_free(&rem);
return 0;
fail:
vfpn_bn_free(&N);
vfpn_bn_free(&D);
vfpn_bn_free(&Q);
vfpn_bn_free(&rem);
return -1;
}
/*
* Decimal digits of |v| into out (most significant first, no terminating
* NUL); returns the digit count or -1 when outcap is too small. The value
* zero yields a single '0'. Groups of nine decimal digits are stripped from
* the low end into a fixed stack (a 640-limb value caps at ~6165 digits, far
* inside 700 groups), then re-emitted top group unpadded, lower groups
* zero-padded to nine.
*/
static __attribute__((unused)) int
vfpn_bn_to_dec(const struct vfpn_bn *v, char *out, size_t outcap)
{
if (vfpn_bn_is_zero(v))
{
if (outcap < 1)
{
return -1;
}
out[0] = '0';
return 1;
}
struct vfpn_bn w;
vfpn_bn_init(&w);
if (vfpn_bn_reserve(&w, v->n) != 0)
{
return -1;
}
w.n = v->n;
if (v->n > 0)
{
__builtin_memcpy(w.d, v->d, v->n * sizeof(uint32_t));
}
uint32_t groups[700];
size_t ngroups = 0;
while (!vfpn_bn_is_zero(&w))
{
if (ngroups >= 700)
{
vfpn_bn_free(&w);
return -1;
}
groups[ngroups++] = vfpn_bn_divmod_small_1e9(&w);
}
vfpn_bn_free(&w);
size_t top = ngroups - 1;
uint32_t t = groups[top];
size_t tdig = 1;
for (uint32_t x = t / 10; x != 0; x /= 10)
{
tdig++;
}
size_t ndigits = tdig + (ngroups - 1) * 9;
if (outcap < ndigits)
{
return -1;
}
size_t pos = 0;
char tmp[10];
size_t ntmp = 0;
do
{
tmp[ntmp++] = (char)('0' + t % 10);
t /= 10;
} while (t != 0);
while (ntmp > 0)
{
out[pos++] = tmp[--ntmp];
}
for (size_t gi = ngroups - 1; gi-- > 0;)
{
uint32_t val = groups[gi];
for (int k = 8; k >= 0; k--)
{
out[pos + (size_t)k] = (char)('0' + val % 10);
val /= 10;
}
pos += 9;
}
return (int)ndigits;
}