A

The point is that float/double in C# (and also in Java, C+, etc.) - double droids♪ They are presented inside as a set of a whole number (mantisses) and a doubling (short). Number 2 may be presented as a binary drab, and 0.02 is not, i.e. it is 1/50 (and a denominator not only double). Therefore, the number 0.02 cannot be accurately presented as float♪What's in the variable? engine.power? There's a binary drab that's closest to 0.02. Let's check this code:float enginepower = 2f / 100;
Console.WriteLine(enginepower * 100 - 2);
He's not giving 0 as it could be expected, but -4470348E-08 (in my car).It means the meaning engine.power actually a little less than two. If you multiply 1,000 results, it'll be a little less than 20, accommodating. int Removes the fragment and results are equal to 19.What should we do? There are a few options.Instead of being unstable to minor errors of removal of the fragment (int)(engine.power * 1000)
use a much more sane rounding:(int)Math.Round(engine.power * 1000)
If you encounter similar problems often, it makes sense to switch to type of data. decimalwhere the numbers inside are stored 10Not a double drab. Consider that this type of data is slower, so there is no negative support for processors.Advanced investigationwith a copy of the assembler exhaust and the specifications.This is my car code:float f = 0.02f;
float ff = f * 1000;
int k = (int)(ff);
Estimated k 20, but this:float f = 0.02f;
int k = (int)(f * 1000);
- 19. (This is in the Debug regime; both versions of the text produce the same assembler code and the same result - 19.) I'm investigating why.The JIT Assembler code is: ; float f = 0.02f;
mov dword ptr [ebp-40h],3CA3D70Ah ; 32bit f = 0.02f
; float ff = f * 1000;
fld dword ptr [ebp-40h] ; extend f to 80bit prec and push
fmul dword ptr ds:[1453D00h] ; multiply by 32bit 1000f
fstp dword ptr [ebp-44h] ; pop and convert 80bit result to 32 bit -> ff
; int k = (int)(ff);
fld dword ptr [ebp-44h] ; extend ff to 80bit prec and push
fstp qword ptr [ebp-50h] ; pop and convert to 64bit -> double temp
movsd xmm0,mmword ptr [ebp-50h] ; extend temp to 128bit and copy to xmm0
cvttsd2si eax,xmm0 ; truncate to 32bit int eax
mov dword ptr [ebp-48h],eax ; store to k
and ; float f = 0.02f;
mov dword ptr [ebp-40h],3CA3D70Ah ; 32bit f = 0.02f
; int k = (int)(f * 1000);
fld dword ptr [ebp-40h] ; extend f to 80bit prec and push
fmul dword ptr ds:[1393CF4h] ; multiply by 32bit 1000f
fstp qword ptr [ebp-4Ch] ; pop and convert to 64bit -> double temp
movsd xmm0,mmword ptr [ebp-4Ch] ; extend temp to 128bit and copy to xmm0
cvttsd2si eax,xmm0 ; truncate to 32bit int eax
mov dword ptr [ebp-44h],eax ; store to k
It's supposed to be like,float32 f = 2f / 100;
float80 r1 = f;
r1 *= float32(1000f);
float32 ff = r1;
r1 = ff; // <-- тут потеря точности
float64 temp = r1;
float128 r2 = temp;
int32 k = (int32)r2;
andfloat32 f = 2f / 100;
float80 r1 = f;
r1 *= float32(1000f);
float64 temp = r1;
float128 r2 = temp;
int32 k = (int32)r2;
The difference, as we see, is that in order to calculate the intermediate result the value is f * 1000 is cut to 32-bit accuracy, and then loaded back to the 80-bit register and downloaded from 64-bit to XMM-registration. In the second version of the code, there is no maintenance of the intermediate result, the code does not cut the value, and up to 64 battles are cut by a more accurate 80-bit value, which results in the difference as a result.The clear authorization for such different calculations is on the language specification §4.1.6 Floating Point Types:Floating-point operations may be performed with higher precision than the result type of the operation. For example, some hardware architectures support an “extended” or “long double” floating-point type with greater range and precision than the double type, and implicitly perform all floating-point operations using this higher precision type.Again on the topic: https://stackoverflow.com/q/8911440/276994 (especially the top reply).