Detailed explanation of php's high precision calculation

PHPer engaged in the financial industry has frequent capital calculation. If you don't pay attention to it, you may lose hundreds of thousands or even worse... Let's take an example:

javascript

Why is 0.1 + 0.2 not equal to 0.3? (correct result: 0.3000000000000004)

Why is 0.8 * 7 not equal to 5.6? (correct result: 5.6000000000005)

PHP

var_dump(intval(0.58 * 100));

The correct result is 57, not 58

Trouble caused by floating point operation

In fact, these results are not language bug s, but they are related to the implementation principle of the language. All the numbers in js are unified as Number, including the fact that the shape is all double type.

PHP will distinguish int from float. No matter what language, as long as floating-point operation is involved, there are similar problems, which must be paid attention to when using.

Note: if you use php + - * / to calculate floating-point numbers, you may encounter some problems with incorrect calculation results, such as echo intval (0.58 * 100) above; you will print 57 instead of 58. This is actually a bug that the binary system at the bottom of the computer cannot accurately represent floating-point numbers, which is cross language. I also encounter this problem with python. So basically, most languages provide a class library or function library for accurate calculation. For example, php has a BC high-precision function library. Later, I will introduce some common BC high-precision functions.

Let's go back to question 57 and 58.

Why is the output 57? Is PHP bug?

To understand this reason, we first need to know the representation of floating-point numbers (IEEE 754):

Floating point number, taking 64 bit length (double precision) as an example, will be represented by 1-bit sign bit (E), 11 digit (Q), 52 digit mantissa (M) (64 bits in total)

Symbol bit: the highest bit represents the positive and negative data, 0 represents the positive number, and 1 represents the negative number.

Index bit: indicates the power of data based on 2, and the index is represented by offset code

Mantissa: the significant number after the decimal point of data

 

The key point here lies in the representation of decimal in binary system. As for how to express decimal in binary system, you can baidu, I will not repeat it here,.

It is important to understand that 0.58 is an infinite value for binary representation (the following number omits the implied 1)

① The binary representation of 0.58 is basically (52 bits): 001010001111111000001010001111111111000001010001111

② The binary representation of 0.57 is basically (52 bits): 0010001111010111000010101000111111111100001010100011110

The binary of the two, if only calculated by these 52 bits, are: 0.58 - > 0.57999999999996 and 0.57 - > 0.5699999999999.

As for the specific floating-point multiplication of 0.58 * 100, we don't think about it so carefully. If you are interested in it, you can see it through mental calculation 0.58 * 100 = 57.999999999

Then you intval, nature is 57 .

It can be seen that the key point of this problem is: "you seem to have a finite number, but it is infinite in the binary representation of the computer."

So, don't think it's a PHP bug anymore, it's just like this ". PHP floating point is in progress + - *% / with inaccuracy

Continue to look at a piece of code:

$a = 0.1;
$b = 0.7;
var_dump(($a + $b) == 0.8); // false

The printed value is boolean false

Why is this?

The PHP manual has the following warnings for floating-point numbers: Warning and floating-point precision

Obviously a simple decimal fraction like 0.1 or 0.7 cannot be converted to an internal binary format without losing a little bit of precision.

This can lead to confusing results: for example, floor((0.1+0.7)*10) usually returns 7 instead of the expected 8, because the internal representation of the result is actually similar to 7.9999999.

This has something to do with the fact that it is impossible to express certain decimal fractions accurately with finite digits.

For example, one third of the decimal system becomes 0.3333333.

So never believe that the result of floating-point numbers is accurate to the last place, and never compare whether two floating-point numbers are equal.

If you really need more precision, you should use arbitrary precision math functions or gmp functions

So the above formula should be rewritten as

$a = 0.1;
$b = 0.7;
var_dump(bcadd($a,$b,2) == 0.8); // true

The commonly used high-precision functions are as follows:

bcadd - add two high precision numbers

bccomp -- compare two high precision numbers and return - 1, 0, 1

bcdiv -- divide two high-precision numbers

bcmod -- high precision digital remainder

bcmul -- multiply two high precision numbers

bcpow -- high precision digital power

bcpowmod -- high precision digital multiplication and modulus, which is very commonly used in number theory

BC scale - configure the default number of decimal places, equivalent to "scale =" in Linux bc

bcsqrt -- find the square root of high precision digit

bcsub -- subtract two high precision numbers

The BC high precision function library includes: add, compare, divide, subtract, remainder, multiply, Nth power, configure the default decimal point number and square.

These functions are useful when it comes to the calculation of money, such as the price calculation of e-commerce.

/**
  * Comparison of two high precision numbers
  * 
  * @access global
  * @param float $left
  * @param float $right
  * @param int $scale Number of decimal places accurate to
  * 
  * @return int $left==$right Back to 0 $left<$right Back to -1 $left>$right Return to 1
  */
var_dump(bccomp($left=4.45, $right=5.54, 2));
// -1
  
 /**
  * Add two high precision numbers
  * 
  * @access global
  * @param float $left
  * @param float $right
  * @param int $scale Number of decimal places accurate to
  * 
  * @return string 
  */
var_dump(bcadd($left=1.0321456, $right=0.0243456, 2));
//1.05
 
  /**
  * Subtraction of two high precision numbers
  * 
  * @access global
  * @param float $left
  * @param float $right
  * @param int $scale Number of decimal places accurate to
  * 
  * @return string 
  */
var_dump(bcsub($left=1.0321456, $right=3.0123456, 2));
//-1.98
  
 /**
  * Division of two high precision numbers
  * 
  * @access global
  * @param float $left
  * @param float $right
  * @param int $scale Number of decimal places accurate to
  * 
  * @return string 
  */
var_dump(bcdiv($left=6, $right=5, 2));
//1.20
 
 /**
  * Multiplication of two high precision numbers
  * 
  * @access global
  * @param float $left
  * @param float $right
  * @param int $scale Number of decimal places accurate to
  * 
  * @return string 
  */
var_dump(bcmul($left=3.1415926, $right=2.4569874566, 2));
//7.71
 
 /**
  * Set the number of decimal places of bc function
  * 
  * @access global
  * @param int $scale Number of decimal places accurate to
  * 
  * @return void 
  */ 
bcscale(3);
var_dump(bcdiv('105', '6.55957')); 
//php7.1 16

Encapsulation method:

/**

 * Exact addition

 * @param [type] $a [description]

 * @param [type] $b [description]

 */

function math_add($a,$b,$scale = '2') {

  return bcadd($a,$b,$scale);

}

/**

 * Precise subtraction

 * @param [type] $a [description]

 * @param [type] $b [description]

 */

function math_sub($a,$b,$scale = '2') {

  return bcsub($a,$b,$scale);

}

/**

 * Exact multiplication

 * @param [type] $a [description]

 * @param [type] $b [description]

 */

function math_mul($a,$b,$scale = '2') {

  return bcmul($a,$b,$scale);

}

/**

 * Exact division

 * @param [type] $a [description]

 * @param [type] $b [description]

 */

function math_p($a,$b,$scale = '2') {

  return bcp($a,$b,$scale);

}

/**

 * Accurate redundancy / modulus

 * @param [type] $a [description]

 * @param [type] $b [description]

 */

function math_mod($a,$b) {

  return bcmod($a,$b);

}

/**

 * Comparative size

 * @param [type] $a [description]

 * @param [type] $b [description]

 * Greater than return 1 equals return 0 less than return - 1

 */

function math_comp($a,$b,$scale = '5') {

  return bccomp($a,$b,$scale); // Compare to decimal places

}

 

Keywords: Programming PHP Javascript Python Linux

Added by tekkenlord on Thu, 16 Apr 2020 13:12:51 +0300