Writing in the front


Recently, the author has been engaged in the development of e-commerce business, because it involves the combination of payment (platform virtual currency payment + wechat/Alipay online payment), so it is necessary to calculate the amount to be paid by different payment methods. This place, where the test came up with an amount bug at the end of last Friday, is what inspired me to write this article.

The bug screenshot is as follows:

The total amount of the order is ¥300.01, of which ¥300 shall be paid with the quota, and the remaining ¥0.01 shall be paid online. However, the payment amount calculated at the “Pay now” button is ¥0.00, which should actually be ¥0.01.

Through the investigation of the code logic, we found the problem.

Const a = 300.01-300 console.log(a) // 0.009999999999990905Copy the code

The result of the above code, unexpected, is the source of the bug. Since the actual amount paid is a repeating decimal, I only truncated the first two digits of the decimal, so the result is 0.00 instead of 0.01 as expected.

Here, the bug is almost solved, but we calm down, in-depth exploration of this problem, find hidden behind the mysterious treasure 🏴☠️.

New look old


9:00 a.m., rooftop of a building in New Territories, breeze not dry

Tony LEUNG: you policemen are really weird. you always like to meet on the rooftop. Andy Lau turns a head: perhaps this is the police 👮♀️. Tony leung looks at Andy Lau: I don’t like this place. Help me. We’ll take him down together. Andy Lau pulls out a gun: sorry, I am police 👮♀️. Tony Leung shouted: Me too!! Andy Lau: : Who knows? We can’t beat him. He’s a 0.3.

Make up a cold joke, combined with the spread of full extensive a problem to understand: 0.1 + 0.2 === 0.3 why is false?

The reason for this problem is the same as the bug in the introduction: floating point problems in the computer world. For what floating point numbers are, please refer to this article I translated: What are floating point numbers? . Now that we have this problem, how can we solve it? Let’s take a closer look at the solution to the floating-point calculation problem.

Use fractional substitutions as units


The most original solution is not to use yuan as the unit of amount, instead of minutes as the unit of amount, so fundamentally there is no decimal problem. When the front end needs to use yuan, such as online payment, it only needs to calculate the amount in minutes first, and then divide by 100 to convert it into yuan. But this is an ideal world, and reality is often not an ideal world. There are often other factors that influence the use of yuan as a monetary unit. For example, the product said that the financial department could easily misunderstand the use of points when looking at the export order form. Or is the back-end said to use points, back-end without some transformation logic, too troublesome. Finally, yuan was used as the unit of amount.

Use toFixed() to convert


Now that we’ve adopted units, we need to consider other ways to avoid floating point calculations. The first one that comes to mind is probably number.prototype.tofixed (), which converts a Number to a string with a specified decimal Number.

(76.211). ToFixed (2) / / ‘76.21’

(76). ToFixed (2) / / ‘76.00’ (76.8450001). The toFixed (2) / / ‘76.85’ (76.845). The toFixed (2) / / ‘76.84’

The toFixed() method is problematic, and the fourth line is not what we expected. Because it’s taking the banker algorithm.

Banker’s algorithm

Four rounding six into five consideration, five after non empty into one, five after empty to see even, five before even should be dropped, five before strange to enter a

Therefore, toFixed() cannot be used directly. If you want to use toFixed(), you need to do the secondary encapsulation yourself.

Use the round method math.floor (), math.ceil (), math.round ()


JS built-in Math module Math, with the above static method. Their function is to return a collated integer of the given number. Note that the return value is an integer.

  1. Math.floor()

    Returns the largest integer less than or equal to a given number, rounded down.

    Math.floor( 45.95); 
    / / 45
    Math.floor( 45.05); 
    / / 45
    Math.floor( 4 ); 
    / / 4
    Math.floor(-45.05); 
    / / - 46
    Math.floor(-45.95); 
    / / - 46
    Copy the code
  2. Math.ceil()

    Returns the smallest integer greater than or equal to a given number, that is, rounded up.

    Math.ceil(95.)
    / / 1
    Math.ceil(4)
    / / 4
    Math.ceil(7.004);
    / / 8
    Math.ceil(-7.004);
    / / - 7
    Copy the code
  3. Math.round()

    The function returns the nearest integer rounded to the number.

    If the decimal part of the argument is greater than 0.5, the adjacent integers with greater absolute values are rounded. If the decimal part of the argument is less than 0.5, it is rounded to adjacent integers with smaller absolute values. If the decimal part of the argument is exactly 0.5, it is rounded to the adjacent integers in the direction of positive infinity. Note that unlike the round() function in many other languages, math.round () is not always rounded away from zero (especially if the fractional part of a negative number is exactly 0.5).

    Math.round(20.49)
    / / 20
    Math.round(20.5)
    / / 21
    Math.round(-20.5)
    / / - 20
    Math.round(-20.51)
    / / - 21
    Copy the code

Now that we know the difference between these three functions and that they all return integers, how do we satisfy our decimal-preserving requirements? The answer is: zoom in, then zoom out.

For example, if the variable a=0.00999999999, we want to keep two decimal places of A and get 0.01.

const a = 0.00999999999
let b = (Math.round(a*100)) / 100
console.log(b)
/ / 0.01
Copy the code

Let’s say we want to keep n decimal places, so we’re going to have 10 to the n.

Extra parseInt() and parseFloat()


  1. parseInt()

    ParseInt (string, radix) parses a string and returns a decimal integer of the specified radix; radix is an integer between 2 and 36, representing the radix of the string being parsed.

    • string

      The value to be parsed. If the argument is not a string, it is converted to a string (using the ToString abstraction operation). Whitespace at the beginning of the string will be ignored.

    • Radix (optional)

      The number from 2 to 36 represents the cardinality of the string. For example, if 16 is specified, the parsed value is a hexadecimal number. Note that 10 is not the default!

  2. parseFloat()

    **parseFloat(string)** Parses an argument (converted to a string if necessary) and returns a float.

    • string

      A value that needs to be parsed into a floating point number.