Question

I can't seem to find anything in the php.net documentation that explains the following results:

$ php -r 'var_dump(bcsub("0.3", "0.2", 4));'
string(6) "0.1000"
$ php -r 'var_dump(bcmul("0.3", "0.2", 4));'
string(4) "0.06"

The subtraction result is exactly what I would expect it to be (I specified a 4-digit scale, and it gave me one in the result). The multiplication result does not (I specified a 4-digit scale, but it gave me a 2-digit one in the result). Why the difference?

Note: I already know how to use number_format(), and I also know that 0.06 === 0.0600 mathematically. I'm only interested in understanding why BC Math seems to act differently with respect to the scale of the result.

Note #2: As mentioned above, number_format() is not an answer to this question, and the answers used in the referenced "duplicate question" all advise using number_format(). I know full well that this function can be used to format the number to the specified precision. I'm just curious to know WHY the return values for these functions have different scales, NOT how to fix them so that they do.

Was it helpful?

Solution

There's a bug in PHP's BCMath function bcmul. It's still present as of PHP 5.5.7, the latest stable release as of this writing.

If you browse the source code (PHP 5.5's BCMath recmul.c), you'll see the relevant function:

void
bc_multiply (bc_num n1, bc_num n2, bc_num *prod, int scale TSRMLS_DC)
{
  bc_num pval; 
  int len1, len2;
  int full_scale, prod_scale;

  /* Initialize things. */
  len1 = n1->n_len + n1->n_scale;
  len2 = n2->n_len + n2->n_scale;
  full_scale = n1->n_scale + n2->n_scale;
  prod_scale = MIN(full_scale,MAX(scale,MAX(n1->n_scale,n2->n_scale)));

  /* Do the multiply */
  _bc_rec_mul (n1, len1, n2, len2, &pval, full_scale TSRMLS_CC);

  /* Assign to prod and clean up the number. */
  pval->n_sign = ( n1->n_sign == n2->n_sign ? PLUS : MINUS );
  pval->n_value = pval->n_ptr;
  pval->n_len = len2 + len1 + 1 - full_scale;
  pval->n_scale = prod_scale;
  _bc_rm_leading_zeros (pval);
  if (bc_is_zero (pval TSRMLS_CC))
    pval->n_sign = PLUS;
  bc_free_num (prod);
  *prod = pval;
}

Note: The word "scale" refers to the number of digits after the separator.

Take a look at the line where prod_scale is assigned. When you invoke bcmul("0.3", "0.2", 4), walking through the code, we see: prod_scale = MIN(2,MAX(4,MAX(1,1)));, so prod_scale is assigned a value of 2.

And, as expected, the function returns a value with two, not four, digits after the decimal place. Unlike in the other BCMath PHP functions (e.g., see lines 63-98 of PHP 5.5's BCMath doaddsub.c), nowhere in this function's logic does it append the trailing zeros.


I've submitted this issue and a patch to the PHP bug tracking system (#66364).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top