Search code examples
phpdivisionzeroscientific-notationbcmath

bcdiv using very small float with scientific notation cause "Division by zero" error


Using bcdiv, i can't divide with small float using scientific notation :

Working code :

bcscale(30);
$a = '1' ;
$b = '0.00000001';
$result = bcdiv($a, $b);
var_dump($result);

Results in :

string(20) "100000000.0000000000"

Non-working code :

bcscale(30);
$a =  '1' ;
$b =  '1e-8';
$result = bcdiv($a, $b);
var_dump($result);

Results in :

Warning: bcdiv() [function.bcdiv]: Division by zero in C:\wamp\www\utilitaires\test_bcdiv.php on line XX NULL

How can i do this division properly, with the less precision loss ?


Solution

  • That is because, actually, bcmath doesn't support scientific notation. It isn't mentioned in manuals, but as you can see, in it's implementation argument conversion is used, it's named php_str2num:

    static void php_str2num(bc_num *num, char *str TSRMLS_DC)
    {
        char *p;
    
        if (!(p = strchr(str, '.'))) {
            bc_str2num(num, str, 0 TSRMLS_CC);
            return;
        }
    
        bc_str2num(num, str, strlen(p+1) TSRMLS_CC);
    }
    

    and so bc_str2num:

    bc_str2num (bc_num *num, char *str, int scale TSRMLS_DC)
    {
      int digits, strscale;
      char *ptr, *nptr;
      char zero_int;
    
      /* Prepare num. */
      bc_free_num (num);
    
      /* Check for valid number and count digits. */
      ptr = str;
      digits = 0;
      strscale = 0;
      zero_int = FALSE;
      if ( (*ptr == '+') || (*ptr == '-'))  ptr++;  /* Sign */
      while (*ptr == '0') ptr++;            /* Skip leading zeros. */
      while (isdigit((int)*ptr)) ptr++, digits++;   /* digits */
      if (*ptr == '.') ptr++;           /* decimal point */
      while (isdigit((int)*ptr)) ptr++, strscale++; /* digits */
      if ((*ptr != '\0') || (digits+strscale == 0))
        {
          *num = bc_copy_num (BCG(_zero_));
          return;
        }
    
      /* Adjust numbers and allocate storage and initialize fields. */
      strscale = MIN(strscale, scale);
      if (digits == 0)
        {
          zero_int = TRUE;
          digits = 1;
        }
      *num = bc_new_num (digits, strscale);
    
      /* Build the whole number. */
      ptr = str;
      if (*ptr == '-')
        {
          (*num)->n_sign = MINUS;
          ptr++;
        }
      else
        {
          (*num)->n_sign = PLUS;
          if (*ptr == '+') ptr++;
        }
      while (*ptr == '0') ptr++;            /* Skip leading zeros. */
      nptr = (*num)->n_value;
      if (zero_int)
        {
          *nptr++ = 0;
          digits = 0;
        }
      for (;digits > 0; digits--)
        *nptr++ = CH_VAL(*ptr++);
    
      /* Build the fractional part. */
      if (strscale > 0)
        {
          ptr++;  /* skip the decimal point! */
          for (;strscale > 0; strscale--)
        *nptr++ = CH_VAL(*ptr++);
        }
    }
    

    -not hard to see that it will fail on scientific notation (well-commented). Perhaps documentation needs to be updated (to mention that implicitly).

    Possible solution will be to convert your string to plain view before applying bcmath functions