preface

  • One of the problems that many of you have encountered is that when the server returns JSON data with decimals, the client uses CGFloat to parse the data. Especially when dealing with sensitive data, this kind of precision loss is completely unacceptable. This article will start from the simple solution and principle, together take you to review this little problem that actually everyone has learned before but forgotten almost.

How to solve the floating point precision problem

Round off


// For example, the server returns this json

{
    "price":1.9
}

// The client parses price and prints 1.9


CGFloat price = model.price

NSLog(@"%f",price)The result is 1.89999999999999Copy the code

At this point, as a salt fish developer, you can write the following code:

NSLog(@"%.2f", price) output:1.89

// Not accurate, no matter, there is a way

NSLog(@"f%@",round(price*10) /10); The output1.9

Copy the code

Of course, there’s also apple’s NSDecimalNumber type for precision issues. NSDecimalNumber is very simple to use. (As for how simple, please baidu, don’t ask me, I don’t baidu also write out)

Better solutions

  • So the question is, as a salt fish of salt fish who is too lazy to write an article for a whole year, which way should I choose to solve this problem?

All right, here we go. Next, code!


// The server must return a string, if it does not: ** Please go to the backend developer with a hammer and gas can **
@property (nonatomic,copy) NSString *price;

Copy the code

Yes, I would choose not to solve this problem and kick the ball out, which is what a qualified developer should do!

Cause of accuracy loss

Solving the floating-point accuracy problem is one aspect, but this is not enough to cover an entire article, so let’s talk about why it is not accurate to parse numbers well.

Most of you can probably tell that the loss of accuracy is due to the way floating-point numbers are stored. After all, this is something that most of you learned in school, but you can just touch your head and say, hey, are you like me? Forgot all about it?

And since there are always some good interviewers and those who have just reviewed this part of the interview who like to pick these little questions to spite us older programmers, let’s review this part of the knowledge.

Floating-point storage

Floating-point types are stored in computers in scientific notation:

Scientific notation represents decimals90.9= >9.09 x 10^1
8.3= >8.3 x 10^0

Copy the code

Let’s take 8.3 as an example. To store 8.3 (8.3 x 10^0), we must first convert 8.3 to base 2, and 8 to binary 1000. What about 0.3?

Old programmer yo, you are not suddenly found unexpectedly forgot how decimal is transformed into base 2 ah, put down your trembling hands ready baidu, you want, I have here!

Take 0.9 as an example, multiply 0.9 by 2, the integer part of the number is the first decimal part of the binary, take the decimal part of the result part and multiply by 2 again, take the integer part of the second digit, and repeat the above operation until the result is equal to 0 or a loop occurs.

0.9 *2 = 1.81position1
0.8 *2 = 1.62position1
0.6 *2 = 1.23position1
0.2 *2 = 0.44position0
0.4 *2 = 0.85position0
0.8 *2 = 1.65position1There is a cycle so0.9The binary of phi is0.1 1100 1100 1100 1100.. Among them1100An infinite loopCopy the code

After the above review we know that 8.3 can be expressed in binary as 1000.0 1001 1001 1001… In scientific notation, 1.000 0 1001 1001 1001 x 2^3

The integer part Index part The decimal part
1 3 000 0 1001 1001 1001

While we know that float is 4 (32-bit) bytes, each of its bits is allocated as follows when stored

The 31st sign bit 23-30 digit (exponential digit) 0-22 bits (decimal place)
1 3 000 0 1001 1001 1001

Significant digits

1. Aaa x 2^b = 0. Aaa x 2^b = 0. Answer :b can be a negative number), then the total number of 23 bits of data can actually represent 24 bits.

  • The largest number that can be expressed with 24 bits is 16777215, and any number greater than that is inexact.

Of course, greater than 16777215 May be completely imprecise, such as 16777216, its binary representation is 1 followed by a heap of zeros. Because it’s an integer power of 2, it’s followed by all zeros so 23 bits can store that number, not if there’s a 1 after 23 zeros, and so on, 16777215 will have a number that satisfies this condition of being an integer power of 2, and it can be expressed correctly.

Although some numbers greater than 16777215 can be expressed accurately, but when we talk about precision, we must be precise, so the number that can be expressed accurately is only between 0 and 16777215, 16777215 is a total of 8 digits, but since the highest digit 1 does not contain all the digits, So accuracy should be 7 significant bits.

Carry the expression range of point of float additionally, this range is affirmation will determine by index, specific is how many I did not calculate, everybody is interested can calculate.

Index storage: shift storage

Since exponents are positive and negative, the number of digits we can represent ranges from -127 to +127, assuming that the first digit is a sign bit

You can see that the representable scope is divided into two parts:

  • 1 000000 0 to 1 111111 1 => -0 to -127

  • 0 000000 0 to 0 111111 1 => +0 to 127

If the highest bit is not used to represent a sign bit, 8 bits can store the range from 0 to 255. Let’s replan the next two ranges:

  • 00000000-01111111 => 0-127 minus 127 indicates the number before -127-0
  • 10000000-11111111 => 128-256 minus 127 indicates the number before 1 to 127

In this way, the +-0 problem is avoided. In the above article, the exponent of 8.3 after converting to a decimal is 3, so 3 is actually stored at 130, which is 10000010. Let’s update the table of actual storage

The 31st sign bit 23-30 digit (exponential digit) 0-22 bits (decimal place)
1 10000010 000 0 1001 1001 1001

Type double

The double type is 8-byte 64-bit and represents only the same number of digits as float. Double represents the following number of digits:

The 63rd symbol bit 52-62 digit (exponential digit) 0-51 bits (decimal place)

Summary: The output result loses the precision reason

Going back to the original 1.9, the number was printed as 1.8999999999999999. Now we should know why, because 1.9 to 2 is an infinite loop, and for storage reasons only 23 bits of the loop are truncated, so the decimal number will definitely be different. And similarly, if you have a decimal after the decimal point that’s a multiple of five, like.5, it’s going to print out a normal decimal, because.5 to base 2 doesn’t go on forever.