Quick introduction to "go" floating-point conversion integral type

Source: Internet
Author: User

Original URL: http://blog.csdn.net/leakers_zzw/article/details/8005762

[note] The existing Intel SSE Directive CVTPS2DQ xmm,xmm/m128 support the source Memory 4 single-precision floating point number into 4 double-character signed integer, the result is sent to the destination register, memory variables must be aligned memory 16 bytes, There are other directives that support the conversion of double-precision and integer-type.

Reproduced in this paper, as a study of C algorithm design.

In computer graphics operations, it is often necessary to convert floating-point numbers to integers, such as in the rasterization phase of an image, to perform a number of type conversions to convert the coordinates represented by floating-point numbers to the screen coordinates of an integer representation. Ok,it ' s so easy:
----------------------------------------------------------------------------------------
//
Forcing type conversions
The decimal part will be cropped off.
//
Int_val = (int) float_val;
----------------------------------------------------------------------------------------
Hey, I'm glad you're as simple as me! The operation was so tiny that I never thought about how it was done until one day someone told me not to use the standard C-type conversion because it was too slow! I was shocked not by the unlucky adventurers who met the Dragon.

The biggest advantage of standard C-type conversions is that it's platform-independent, whether it's running on a X86 or running on a PowerPC, you don't have to worry about anything, the compiler will do everything for you. And that's exactly the biggest drawback-it relies heavily on the implementation of the compiler. The actual test shows that the compiler generated the code, its speed is not satisfactory.

An alternative approach is to manipulate the data bits directly. If you are familiar with the representation of IEEE floating-point numbers (if you do not know anything at all, please check the information in the appendix at the end of the article), it is obvious. It extracts the exponent and the mantissa, and then performs a shift operation on the mantissa. The code is as follows:
----------------------------------------------------------------------------------------
//
Converts 32-bit floating-point fval to 32-bit integers and stores them in Ival
The decimal part will be cropped off.
//
void TruncToInt32 (int &ival, float fval)
{
ival = * (int *) &fval;

Extract Mantissa
Note that there is an omitted 1 in front of the actual mantissa.
int mantissa = (ival & 0x07fffff) | 0x800000

Extract Index
Divide by 23, the exponent is greater than 23, move left, or right
Since the exponent is represented by an offset, the 23+127=150
int exponent = Ival-((>>) & 0xff);

if (Exponent < 0)
Ival = (mantissa <<-exponent);
Else
Ival = (mantissa >> exponent);

If less than 0, the result is reversed
if ((* (int *) &fval) & 0x80000000)
Ival =-ival;
}
----------------------------------------------------------------------------------------
The function has a bug, that is, when fval=0, the return value is 2. The reason is that for statement mantissa>>exponent, the compiler uses a cyclic shift instruction. The solution is to either treat the 0 as a special treatment or use the assembly as a direct implementation.

This function is faster than the standard C conversion and can be run in parallel with the FPU because the whole process uses only integer arithmetic. However, the disadvantage is that (1) depends on the hardware platform. For example, depending on the small tail and the big tail order, the function should be modified accordingly. (2) for float and double, different implementations are used because the data bits are different. (3) for float, only 24 valid values are retained, although int has 32 bits.

The quicker way is to use the FPU Directive FISTP, which pops up the floating-point number in the stack and saves it as an integer:
----------------------------------------------------------------------------------------
//
Converts 64-bit floating-point fval to 32-bit integers and stores them in Ival
The fractional part will be rounded to an even number
//
inline void Roundtointfpu (int &ival, double fval)
{
_asm
{
FLD fval
mov edx, DWORD ptr [Ival]
FISTP DWORD ptr [edx]
}
}
----------------------------------------------------------------------------------------
Well, both speed and precision seem to be quite satisfying. But if you look at it in a different way, the FISTP directive requires 6 cycle, and the floating-point multiplication only requires 3 cycle! Worse, when the FISTP is running, it must occupy the FPU, which means that other floating-point operations will not execute. Just for a one-type conversion operation is to pay such a big price, light think it feels distressed.

Of course, it also has many advantages: faster speed, more accurate values (rounded to even numbers), stronger applicability (both float and double). It is important to note that the FPU default rounding to even numbers (round to even) is different from what we normally call rounding (round). The difference between the two is in the processing of intermediate values. Consider the decimal 3.5 and 4.5. Rounding to even numbers tends to be adjacent even, so the result of rounding is 3.5->4,4.5->4; and the traditional rounding is 3.5->4,4.5->5. Rounding to even numbers can result in more accurate, more stable values.

Besides, is there a better, faster way? Yes, that's the gorgeous Magic number!

Take a look at the following function:
----------------------------------------------------------------------------------------
//
Convert 64-bit floating-point numbers to 32-bit integers
The fractional part will be rounded to an even number
//
inline void RoundToInt64 (int &val, double dval)
{
Static Double magic = 6755399441055744.0;
Dval + = Magic;
val = * (int*) &dval;
}
----------------------------------------------------------------------------------------
Come on, that's the greatest part of it! All you need is a floating-point addition, what can you expect?

Of course, never use inexplicable code, and now let's see how it turns out to be a trick.

First, we need to figure out how the FPU performs floating-point addition. Consider 8.75 plus 2^23. The binary representation of 8.75 is 1000.11, and the conversion to IEEE standard floating-point number format is 1.00011*2^3. Assume that both are 32-bit floats, which are stored in memory as:
----------------------------------------------------------------------------------------
Sign Bit (31), exponent (30-23), Mantissa (22-0)

8.75:0, 10000010, 0001 1000 0000 0000 0000 000

2^23:0, 10010110,0000 0000 0000 0000 0000 000
----------------------------------------------------------------------------------------
The actions performed by the FPU are: (1) The index of the smaller index is raised, the index is the same, (2) The mantissa is added, and (3) The result is normalized to the standard format. Let's look at the whole process in detail:
----------------------------------------------------------------------------------------
1, increase the index of 8.75, the mantissa should be shifted to the right 23-3 = 20 bits:

8.75 = 1.00011*2^3 =. 0000000000000000000100011*2^23

2, add the two. It is important to note that
Since float has only 23-bit mantissa, the low of 8.75 is removed:

8.75:0, 10010110, 0000 0000 0000 0000 0001 000
+
2^23:0, 10010110,0000 0000 0000 0000 0000 000
=
0, 10010110, 0000 0000 0000 0000 0001 000

3, will be normalized to the standard format:

Don't forget that 2^23 also has a leading 1, so the result is regular, no action required
----------------------------------------------------------------------------------------
Have you found anything yet? Yes, the end of the result part of the extraction, it is an int (8.75)! Yes, the mystery of magic number is here, by forcing the FPU to shift the mantissa to get the results we need.

But don't get too excited, let's take a look at negative numbers. Take 8.75 For example, 8.75 plus 2^23 equals 2^23 minus 8.75, as follows:
-------------------------------------------------------------------- --------------------
2^23:0, 10010110,0000 0000 0000 0000 0000
-
8.75:0, 10010110, 0000 0000 0000 0000 0001
=
0, 10010110, 1111 1111 1111 1111 1110
------------------------------------------------------------ ----------------------------
is good, the tail part is the complement of INT (-8.75) =-8. However, 2^23 's leading 1 is borrow in the subtraction process, so the mantissa in front of the result is not 1,FPU to perform the regular operation, and finally we get:
-------------------------------------------------- --------------------------------------
0, 10010110, 1111 1111 1111 1111 1100
------------------------------ ----------------------------------------------------------
Fall Short! Wait, if the highest position of the mantissa of the 2^23 is 1, then the leading 1 will not be preserved in the subtraction process? Absolutely right, that's what we need. So the last magic number is 0,10010110,1000 0000 0000 0000 0000 000, which is 1.5*2^23.

However, we need to understand some of the limitations of this approach. First, when you save the float value of the result as an integer, we need to mask the 22-31 bits with some mask values. Second, float can only hold a valid value of up to 23 bits, and when the highest position of the mantissa is 1, the valid values are reduced to 22 bits, which means that we are powerless over values greater than 2^23-1.

In contrast, if we only deal with double, then all the problems will be solved. First, the exponential bit of double, the sign bit and the highest bit of the mantissa are all 32 bits high, without masking operations. Second, double has up to 52 bits of mantissa, for 32-bit int, it is too extravagant!

The magic number for Double is 1.5*2^52=6755399441055744.0, and the derivation process is the same.

Based on the same principle, we can also calculate the magic number that converts float and double to fixed-point numbers. For example, for a 16-16 fixed-point number, the mantissa shifts the number of digits to the right by 16 less than the original, so for double, the corresponding magic number is 1.5*2^36. If you want to convert to a fixed-point number of 8-24, the number of shifts is less 24,magic numbers is 1.5*2^28. For fixed-point numbers in other formats, and so on. Compared to an expression such as int (float_val*65536), both speed and precision are much better.

Again, you have to reiterate that for values that are removed in the final result, the FPU rounds them to an even number. However, there are times when we really need to do things like floor and ceil. It is simple to add or subtract a correction value from the original number, as shown below:
----------------------------------------------------------------------------------------
Fixed value
static double magic_delta=0.499999999999;

Intercept to Integer
inline void Floor64 (int &val, double dval)
{
RoundToInt64 (Val,dval-delta);
}

Rounding to integers
inline void Ceil64 (int &val, double dval)
{
RoundToInt64 (Val,dval+delta);
}
----------------------------------------------------------------------------------------
There is no free lunch in the world. We get the speed, and correspondingly, we have to pay some price, that is portability. All of the above methods are based on the following assumptions: (1) run on x86, (2) meet the IEEE floating-point number Standard, (3) int is 32 bits, float is 32 bits, double is 64 bits. The limitations are actually quite large, in contrast, int_val= (int) float_val will be much more convenient. Of course, you can also use conditional compilation.

Finally, let's look at two sets of actual test values. The report comes from Sree Kotay and his friend Herf:
----------------------------------------------------------------------------------------
Platform: Pentium iv/1.2

Conversion of 64-bit floating-point numbers to 32-bit integers:

Int (f): 2772.65 ms
fistp:679.269 ms
Magic number:622.519 ms

Conversion of 64-bit floating-point numbers to 16-16-point number:

Int (f*65536): 2974.57 ms
fistp:3100.84 ms
Magic number:606.80 ms

Floor function:

ANSI floor:7719.400 ms
Magic number:687.177 ms
----------------------------------------------------------------------------------------
Platform: Pentium d/3.2

Conversion of 64-bit floating-point numbers to 32-bit integers:

Int (f): 1948.470 ms
fistp:523.792 ms
Magic number:333.033 ms

Conversion of 64-bit floating-point numbers to 16-16-point number:

Int (f*65536): 2163.56 ms
fistp:7103.66 ms
Magic number:335.03 ms

Floor function:

ANSI floor:3771.55 ms
Magic number:380.25 ms
----------------------------------------------------------------------------------------
The difference in performance is really surprising, isn't it? What I want to say is that the guy who writes the compiler is definitely not a fool (I'm afraid it's much smarter than you and me) and they certainly know all of these excellent algorithms. But why is the compiler behaving so badly? One reason is that, in order to make floating-point operations as accurate and fast as possible, IEEE has a lot of limitations on the choice of algorithms. On the other hand, the rounding rules of the IEEE (rounded to even numbers), although excellent in terms of maintaining the consistency of the floating-point number, do not conform to the description (truncated) of ANSI C on the type conversion of floating-point numbers to integers. As a result, the compiler has to do a lot of work to ensure that the behavior is correct (conforming to the standard). This is not a problem in most cases--unless you are writing graphics/sound/multimedia code.

I've just actually tested it on my Celeron 1.1G. The compiler is VC2003, using Performencecounter to calculate the time, in debug mode, the C standard conversion/fistp/magicnumber three methods time-consuming ratio of about 5/3/2. But in the release mode of optimized full open, the standard C type conversion speed is far ahead of all other methods! I don't know if I have a problem with my test method, or I think it's a great vs.
--------------------------------------------------------------------------------------
Reference documents:
1,what every computer scientist should Know about floating-point arithmetic by David Goldberg (PDF):
This paper contains everything about floating-point numbers, as its name implies, any document that anyone who wants to know about floating-point numbers must read. (The discussion of precision has really benefited me.) )

2,let's Get to the floating point by Chris Hecker (PDF):
The early articles on floating-point numbers were very well written and worth a look.

3,IEEE Standard 754 for Binary floating point arithmetic by Prof.w.kahan (PDF):
A description of the IEEE754 standard.

4,ia64 floating point Operations and the IEEE standard for Binary floating point Arithmetic (PDF):
Floating-point number implementation for IA64.

5,know Your fpu:fixing Floating Fast by Sree Kot

Quick introduction to "go" floating-point conversion integral type

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.