Skip to content

Instantly share code, notes, and snippets.

@jrfnl
Last active October 17, 2019 01:13
Show Gist options
  • Save jrfnl/8449978 to your computer and use it in GitHub Desktop.
Save jrfnl/8449978 to your computer and use it in GitHub Desktop.
Do simple reliable math calculations without the risk of wrong results using bcmath
<?php
/**
* Do simple reliable floating point calculations without the risk of wrong results.
*
* @see http://floating-point-gui.de/
* @see the big red warning on http://php.net/language.types.float.php
*
* In the rare case that the bcmath extension would not be loaded, it will return the
* normal calculation results.
*
* @see Source: https://gist.github.com/jrfnl/8449978
*
* @param mixed $number1 Scalar (string/int/float/bool).
* @param string $action Calculation action to execute. Valid input:
* '+' or 'add' or 'addition',
* '-' or 'sub' or 'subtract',
* '*' or 'mul' or 'multiply',
* '/' or 'div' or 'divide',
* '%' or 'mod' or 'modulus',
* '=' or 'comp' or 'compare'.
* @param mixed $number2 Scalar (string/int/float/bool).
* @param bool $round Whether or not to round the result. Defaults to false.
* Will be disregarded for a compare operation.
* @param int $decimals Decimals for rounding operation. Defaults to 0.
* @param int $precision Calculation precision. Defaults to 10.
*
* @return int|float|false Calculation result or false if either of the numbers isn't scalar or
* an invalid operation was passed.
* - for compare the result will always be an integer.
* - for all other operations, the result will either be an integer (preferred)
* or a float.
*/
function bc_calc( $number1, $action, $number2, $round = false, $decimals = 0, $precision = 10 ) {
if ( ! is_scalar( $number1 ) || ! is_scalar( $number2 ) ) {
return false;
}
$bc = extension_loaded( 'bcmath' );
if ( $bc ) {
$number1 = number_format( $number1, 10, '.', '' );
$number2 = number_format( $number2, 10, '.', '' );
}
$result = null;
$compare = false;
switch ( $action ) {
case '+':
case 'add':
case 'addition':
$result = ( $bc ) ? bcadd( $number1, $number2, $precision ) /* string */ : ( $number1 + $number2 );
break;
case '-':
case 'sub':
case 'subtract':
$result = ( $bc ) ? bcsub( $number1, $number2, $precision ) /* string */ : ( $number1 - $number2 );
break;
case '*':
case 'mul':
case 'multiply':
$result = ( $bc ) ? bcmul( $number1, $number2, $precision ) /* string */ : ( $number1 * $number2 );
break;
case '/':
case 'div':
case 'divide':
if ( $bc ) {
$result = bcdiv( $number1, $number2, $precision ); // String, or NULL if right_operand is 0.
}
else if ( $number2 != 0 ) {
$result = $number1 / $number2;
}
if ( ! isset( $result ) ) {
$result = 0;
}
break;
case '%':
case 'mod':
case 'modulus':
if ( $bc ) {
$result = bcmod( $number1, $number2, $precision ); // String, or NULL if modulus is 0.
}
else if ( $number2 != 0 ) {
$result = $number1 % $number2;
}
if ( ! isset( $result ) ) {
$result = 0;
}
break;
case '=':
case 'comp':
case 'compare':
$compare = true;
if ( $bc ) {
$result = bccomp( $number1, $number2, $precision ); // Returns int 0, 1 or -1.
}
else {
$result = ( $number1 == $number2 ) ? 0 : ( ( $number1 > $number2 ) ? 1 : -1 );
}
break;
}
if ( isset( $result ) ) {
if ( $compare === false ) {
if ( $round === true ) {
$result = round( floatval( $result ), $decimals );
if ( $decimals === 0 ) {
$result = (int) $result;
}
}
else {
$result = ( intval( $result ) == $result ) ? intval( $result ) : floatval( $result );
}
}
return $result;
}
return false;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment