void sqlnum_from_str()

in driver/utility.cc [2684:2832]


void sqlnum_from_str(const char *numstr, SQL_NUMERIC_STRUCT *sqlnum,
                     int *overflow_ptr)
{
  /*
     We use 16 bits of each integer to convert the
     current segment of the number leaving extra bits
     to multiply/carry
  */
  unsigned build_up[8], tmp_prec_calc[8];
  /* current segment as integer */
  unsigned int curnum;
  /* current segment digits copied for strtoul() */
  char curdigs[5];
  /* number of digits in current segment */
  int usedig;
  int i;
  int len;
  char *decpt= strchr((char*)numstr, '.');
  int overflow= 0;
  SQLSCHAR reqscale= sqlnum->scale;
  SQLCHAR reqprec= sqlnum->precision;

  memset(&sqlnum->val, 0, sizeof(sqlnum->val));
  memset(build_up, 0, sizeof(build_up));

  /* handle sign */
  if (!(sqlnum->sign= !(*numstr == '-')))
    ++numstr;

  len= (int) strlen(numstr);
  sqlnum->precision= len;
  sqlnum->scale= 0;

  /* process digits in groups of <=4 */
  for (i= 0; i < len; i += usedig)
  {
    if (i + 4 < len)
      usedig= 4;
    else
      usedig= len - i;
    /*
       if we have the decimal point, ignore it by setting it to the
       last char (will be ignored by strtoul)
    */
    if (decpt && decpt >= numstr + i && decpt < numstr + i + usedig)
    {
      usedig = (int) (decpt - (numstr + i) + 1);
      sqlnum->scale= len - (i + usedig);
      --sqlnum->precision;
      decpt= NULL;
    }

    if (overflow)
      goto end;

    /* grab just this piece, and convert to int */
    memcpy(curdigs, numstr + i, usedig);
    curdigs[usedig]= 0;
    curnum= strtoul(curdigs, NULL, 10);
    if (curdigs[usedig - 1] == '.')
      sqlnum_scale(build_up, usedig - 1);
    else
      sqlnum_scale(build_up, usedig);
    /* add the current number */
    build_up[0] += curnum;
    sqlnum_carry(build_up);
    if (build_up[7] & ~0xffff)
      overflow= 1;
  }

  /* scale up to SQL_DESC_SCALE */
  if (reqscale > 0 && reqscale > sqlnum->scale)
  {
    while (reqscale > sqlnum->scale)
    {
      sqlnum_scale(build_up, 1);
      sqlnum_carry(build_up);
      ++sqlnum->scale;
    }
  }
  /* scale back, truncating decimals */
  else if (reqscale < sqlnum->scale)
  {
    while (reqscale < sqlnum->scale && sqlnum->scale > 0)
    {
      sqlnum_unscale_le(build_up);

      // Value 2 of overflow indicates truncation, not critical
      if (build_up[0] % 10)
        overflow = 2;

      build_up[0] /= 10;
      --sqlnum->precision;
      --sqlnum->scale;
    }
  }

  /* scale back whole numbers while there's no significant digits */
  if (reqscale < 0)
  {
    memcpy(tmp_prec_calc, build_up, sizeof(build_up));
    while (reqscale < sqlnum->scale)
    {
      sqlnum_unscale_le(tmp_prec_calc);
      if (tmp_prec_calc[0] % 10)
      {
        overflow= 1;
        goto end;
      }
      sqlnum_unscale_le(build_up);
      tmp_prec_calc[0] /= 10;
      build_up[0] /= 10;
      --sqlnum->precision;
      --sqlnum->scale;
    }
  }

  /* calculate minimum precision */
  memcpy(tmp_prec_calc, build_up, sizeof(build_up));

  {
    SQLCHAR temp_precision = sqlnum->precision;

    do
    {
      sqlnum_unscale_le(tmp_prec_calc);
      i= tmp_prec_calc[0] % 10;
      tmp_prec_calc[0] /= 10;
      if (i == 0)
        --temp_precision;
    } while (i == 0 && temp_precision > 0);

    /* detect precision overflow */
    if (temp_precision > reqprec)
      overflow= 1;
  }

  /* compress results into SQL_NUMERIC_STRUCT.val */
  for (i= 0; i < 8; ++i)
  {
    int elem= 2 * i;
    sqlnum->val[elem]= build_up[i] & 0xff;
    sqlnum->val[elem+1]= (build_up[i] >> 8) & 0xff;
  }

end:
  if (overflow_ptr)
    *overflow_ptr= overflow;
}