diff options
Diffstat (limited to 'src/mpack/conv.c')
| -rw-r--r-- | src/mpack/conv.c | 375 | 
1 files changed, 375 insertions, 0 deletions
| diff --git a/src/mpack/conv.c b/src/mpack/conv.c new file mode 100644 index 0000000000..203b13fadb --- /dev/null +++ b/src/mpack/conv.c @@ -0,0 +1,375 @@ +#include "conv.h" + +static int mpack_fits_single(double v); +static mpack_value_t mpack_pack_ieee754(double v, unsigned m, unsigned e); +static int mpack_is_be(void) FPURE; +static double mpack_fmod_pow2_32(double a); + + +#define POW2(n) \ +  ((double)(1 << (n / 2)) * (double)(1 << (n / 2)) * (double)(1 << (n % 2))) + +#define MPACK_SWAP_VALUE(val)                                  \ +  do {                                                         \ +    mpack_uint32_t lo = val.lo;                                \ +    val.lo = val.hi;                                           \ +    val.hi = lo;                                               \ +  } while (0) + +MPACK_API mpack_token_t mpack_pack_nil(void) +{ +  mpack_token_t rv; +  rv.type = MPACK_TOKEN_NIL; +  return rv; +} + +MPACK_API mpack_token_t mpack_pack_boolean(unsigned v) +{ +  mpack_token_t rv; +  rv.type = MPACK_TOKEN_BOOLEAN; +  rv.data.value.lo = v ? 1 : 0; +  rv.data.value.hi = 0; +  return rv; +} + +MPACK_API mpack_token_t mpack_pack_uint(mpack_uintmax_t v) +{ +  mpack_token_t rv; +  rv.data.value.lo = v & 0xffffffff; +  rv.data.value.hi = (mpack_uint32_t)((v >> 31) >> 1); +  rv.type = MPACK_TOKEN_UINT; +  return rv; +} + +MPACK_API mpack_token_t mpack_pack_sint(mpack_sintmax_t v) +{ +  if (v < 0) { +    mpack_token_t rv; +    mpack_uintmax_t tc = -((mpack_uintmax_t)(v + 1)) + 1; +    tc = ~tc + 1; +    rv = mpack_pack_uint(tc); +    rv.type = MPACK_TOKEN_SINT; +    return rv; +  } + +  return mpack_pack_uint((mpack_uintmax_t)v); +} + +MPACK_API mpack_token_t mpack_pack_float_compat(double v) +{ +  /* ieee754 single-precision limits to determine if "v" can be fully +   * represented in 4 bytes */ +  mpack_token_t rv; + +  if (mpack_fits_single(v)) { +    rv.length = 4; +    rv.data.value = mpack_pack_ieee754(v, 23, 8); +  } else { +    rv.length = 8; +    rv.data.value = mpack_pack_ieee754(v, 52, 11); +  } + +  rv.type = MPACK_TOKEN_FLOAT; +  return rv; +} + +MPACK_API mpack_token_t mpack_pack_float_fast(double v) +{ +  /* ieee754 single-precision limits to determine if "v" can be fully +   * represented in 4 bytes */ +  mpack_token_t rv; + +  if (mpack_fits_single(v)) { +    union { +      float f; +      mpack_uint32_t m; +    } conv; +    conv.f = (float)v; +    rv.length = 4; +    rv.data.value.lo = conv.m; +    rv.data.value.hi = 0; +  } else { +    union { +      double d; +      mpack_value_t m; +    } conv; +    conv.d = v; +    rv.length = 8; +    rv.data.value = conv.m; +    if (mpack_is_be()) { +      MPACK_SWAP_VALUE(rv.data.value); +    } +  } + +  rv.type = MPACK_TOKEN_FLOAT; +  return rv; +} + +MPACK_API mpack_token_t mpack_pack_number(double v) +{ +  mpack_token_t tok; +  double vabs; +  vabs = v < 0 ? -v : v; +  assert(v <= 9007199254740991. && v >= -9007199254740991.); +  tok.data.value.hi = (mpack_uint32_t)(vabs / POW2(32)); +  tok.data.value.lo = (mpack_uint32_t)mpack_fmod_pow2_32(vabs); + +  if (v < 0) { +    /* Compute the two's complement */ +    tok.type = MPACK_TOKEN_SINT; +    tok.data.value.hi = ~tok.data.value.hi; +    tok.data.value.lo = ~tok.data.value.lo + 1; +    if (!tok.data.value.lo) tok.data.value.hi++; +    if (tok.data.value.lo == 0 && tok.data.value.hi == 0) tok.length = 1; +    else if (tok.data.value.lo < 0x80000000) tok.length = 8; +    else if (tok.data.value.lo < 0xffff7fff) tok.length = 4; +    else if (tok.data.value.lo < 0xffffff7f) tok.length = 2; +    else tok.length = 1; +  } else { +    tok.type = MPACK_TOKEN_UINT; +    if (tok.data.value.hi) tok.length = 8; +    else if (tok.data.value.lo > 0xffff) tok.length = 4; +    else if (tok.data.value.lo > 0xff) tok.length = 2; +    else tok.length = 1; +  } + +  if (mpack_unpack_number(tok) != v) { +    return mpack_pack_float(v); +  } + +  return tok; +} + +MPACK_API mpack_token_t mpack_pack_chunk(const char *p, mpack_uint32_t l) +{ +  mpack_token_t rv; +  rv.type = MPACK_TOKEN_CHUNK; +  rv.data.chunk_ptr = p; +  rv.length = l; +  return rv; +} + +MPACK_API mpack_token_t mpack_pack_str(mpack_uint32_t l) +{ +  mpack_token_t rv; +  rv.type = MPACK_TOKEN_STR; +  rv.length = l; +  return rv; +} + +MPACK_API mpack_token_t mpack_pack_bin(mpack_uint32_t l) +{ +  mpack_token_t rv; +  rv.type = MPACK_TOKEN_BIN; +  rv.length = l; +  return rv; +} + +MPACK_API mpack_token_t mpack_pack_ext(int t, mpack_uint32_t l) +{ +  mpack_token_t rv; +  rv.type = MPACK_TOKEN_EXT; +  rv.length = l; +  rv.data.ext_type = t; +  return rv; +} + +MPACK_API mpack_token_t mpack_pack_array(mpack_uint32_t l) +{ +  mpack_token_t rv; +  rv.type = MPACK_TOKEN_ARRAY; +  rv.length = l; +  return rv; +} + +MPACK_API mpack_token_t mpack_pack_map(mpack_uint32_t l) +{ +  mpack_token_t rv; +  rv.type = MPACK_TOKEN_MAP; +  rv.length = l; +  return rv; +} + +MPACK_API bool mpack_unpack_boolean(mpack_token_t t) +{ +  return t.data.value.lo || t.data.value.hi; +} + +MPACK_API mpack_uintmax_t mpack_unpack_uint(mpack_token_t t) +{ +  return (((mpack_uintmax_t)t.data.value.hi << 31) << 1) | t.data.value.lo; +} + +/* unpack signed integer without relying on two's complement as internal + * representation */ +MPACK_API mpack_sintmax_t mpack_unpack_sint(mpack_token_t t) +{ +  mpack_uint32_t hi = t.data.value.hi; +  mpack_uint32_t lo = t.data.value.lo; +  mpack_uintmax_t rv = lo; +  assert(t.length <= sizeof(mpack_sintmax_t)); + +  if (t.length == 8) { +    rv |= (((mpack_uintmax_t)hi) << 31) << 1; +  } +  /* reverse the two's complement so that lo/hi contain the absolute value. +   * note that we have to mask ~rv so that it reflects the two's complement +   * of the appropriate byte length */ +  rv = (~rv & (((mpack_uintmax_t)1 << ((t.length * 8) - 1)) - 1)) + 1; +  /* negate and return the absolute value, making sure mpack_sintmax_t can +   * represent the positive cast. */ +  return -((mpack_sintmax_t)(rv - 1)) - 1; +} + +MPACK_API double mpack_unpack_float_compat(mpack_token_t t) +{ +  mpack_uint32_t sign; +  mpack_sint32_t exponent, bias; +  unsigned mantbits; +  unsigned expbits; +  double mant; + +  if (t.data.value.lo == 0 && t.data.value.hi == 0) +    /* nothing to do */ +    return 0; + +  if (t.length == 4) mantbits = 23, expbits = 8; +  else mantbits = 52, expbits = 11; +  bias = (1 << (expbits - 1)) - 1; + +  /* restore sign/exponent/mantissa */ +  if (mantbits == 52) { +    sign = t.data.value.hi >> 31; +    exponent = (t.data.value.hi >> 20) & ((1 << 11) - 1); +    mant = (t.data.value.hi & ((1 << 20) - 1)) * POW2(32); +    mant += t.data.value.lo; +  } else { +    sign = t.data.value.lo >> 31; +    exponent = (t.data.value.lo >> 23) & ((1 << 8) - 1); +    mant = t.data.value.lo & ((1 << 23) - 1); +  } + +  mant /= POW2(mantbits); +  if (exponent) mant += 1.0; /* restore leading 1 */ +  else exponent = 1; /* subnormal */ +  exponent -= bias; + +  /* restore original value */ +  while (exponent > 0) mant *= 2.0, exponent--; +  while (exponent < 0) mant /= 2.0, exponent++; +  return mant * (sign ? -1 : 1); +} + +MPACK_API double mpack_unpack_float_fast(mpack_token_t t) +{ +  if (t.length == 4) { +    union { +      float f; +      mpack_uint32_t m; +    } conv; +    conv.m = t.data.value.lo; +    return conv.f; +  } else { +    union { +      double d; +      mpack_value_t m; +    } conv; +    conv.m = t.data.value; +     +    if (mpack_is_be()) { +      MPACK_SWAP_VALUE(conv.m); +    } + +    return conv.d; +  } +} + +MPACK_API double mpack_unpack_number(mpack_token_t t) +{ +  double rv; +  mpack_uint32_t hi, lo; +  if (t.type == MPACK_TOKEN_FLOAT) return mpack_unpack_float(t); +  assert(t.type == MPACK_TOKEN_UINT || t.type == MPACK_TOKEN_SINT); +  hi = t.data.value.hi; +  lo = t.data.value.lo; +  if (t.type == MPACK_TOKEN_SINT) { +    /* same idea as mpack_unpack_sint, except here we shouldn't rely on +     * mpack_uintmax_t having 64-bits, operating on the 32-bit words separately. +     */ +    if (!hi) { +      assert(t.length <= 4); +      hi = 0; +      lo = (~lo & (((mpack_uint32_t)1 << ((t.length * 8) - 1)) - 1)); +    } else { +      hi = ~hi; +      lo = ~lo; +    } +    lo++; +    if (!lo) hi++; +  } +  rv = (double)lo + POW2(32) * hi; +  return t.type == MPACK_TOKEN_SINT ? -rv : rv; +} + +static int mpack_fits_single(double v) +{ +  return (float)v == v; +} + +static mpack_value_t mpack_pack_ieee754(double v, unsigned mantbits, +    unsigned expbits) +{ +  mpack_value_t rv = {0, 0}; +  mpack_sint32_t exponent, bias = (1 << (expbits - 1)) - 1; +  mpack_uint32_t sign; +  double mant; + +  if (v == 0) { +    rv.lo = 0; +    rv.hi = 0; +    goto end; +  } + +  if (v < 0) sign = 1, mant = -v; +  else sign = 0, mant = v; + +  exponent = 0; +  while (mant >= 2.0) mant /= 2.0, exponent++; +  while (mant < 1.0 && exponent > -(bias - 1)) mant *= 2.0, exponent--; + +  if (mant < 1.0) exponent = -bias; /* subnormal value */ +  else mant = mant - 1.0; /* remove leading 1 */ +  exponent += bias; +  mant *= POW2(mantbits); + +  if (mantbits == 52) { +    rv.hi = (mpack_uint32_t)(mant / POW2(32)); +    rv.lo = (mpack_uint32_t)(mant - rv.hi * POW2(32)); +    rv.hi |= ((mpack_uint32_t)exponent << 20) | (sign << 31); +  } else if (mantbits == 23) { +    rv.hi = 0; +    rv.lo = (mpack_uint32_t)mant; +    rv.lo |= ((mpack_uint32_t)exponent << 23) | (sign << 31); +  } + +end: +  return rv; +} + +static int mpack_is_be(void) +{ +  union { +    mpack_uint32_t i; +    char c[sizeof(mpack_uint32_t)]; +  } test; + +  test.i = 1; +  return test.c[0] == 0; +} + +/* this simplified version of `fmod` that returns the remainder of double + * division by 0xffffffff, which is enough for our purposes */ +static double mpack_fmod_pow2_32(double a) +{ +  return a - ((double)(mpack_uint32_t)(a / POW2(32)) * POW2(32)); +} | 
