* * *

Author Topic: Testing if a float has a fractional part  (Read 1290 times)

Gammatester

  • Jr. Member
  • **
  • Posts: 57
Testing if a float has a fractional part
« on: May 15, 2018, 04:19:02 pm »
After the broken frac discussion on the developer mailing list, there is another thread on fpc-pascal called "Testing if a float has a fractional part" (https://www.mail-archive.com/fpc-pascal@lists.freepascal.org/msg48697.html).

Unfortunately you can read:

Jonas Maebe wrote: "The only proper way is to use something like Math.SameValue(x, int(x))"

Sven Barth wrote "Thus it is safer (and more portable) to use Math.SameValue()."

This is incorrect. For double precision on 64-bit systems it is obviuosly incorrect for small values x with absolute values less than the machine epsilon (~2.22e-16) because int(x) is zero and SameValue evaluates to true, although the fractional part is manifestly non-zero.

It is also incorrect for values larger than 1.

Here a test program for 64-bit compilers (on systems with extended SameValue is evaluated in extended precision, and therefore the first example give FALSE).

Code: Pascal  [Select]
  1. uses
  2.   math;
  3. var
  4.   x,y: double;
  5. begin
  6.   writeln('Testing if a float has a zero fractional part');
  7.   writeln('----------------------------------------');
  8.   y := ldexp(1,-40);
  9.   x := 1+y;
  10.   writeln('Tests for x = ',x);
  11.   writeln('Using SameValue: ',SameValue(x,int(x)));
  12.   writeln('   Using frac  : ',frac(x)=0);
  13.   writeln(' Value of frac : ', frac(x):30);
  14.   writeln;
  15.   x := 1e-20;
  16.   writeln('Tests for x = ',x);
  17.   writeln('Using SameValue: ',SameValue(x,int(x)));
  18.   writeln('   Using frac  : ',frac(x)=0);
  19.   writeln(' Value of frac : ', frac(x):30);
  20. end.
  21.  
and the result

Code: [Select]
Testing if a float has a zero fractional part
----------------------------------------
Tests for x =  1.0000000000009095E+000
Using SameValue: TRUE
   Using frac  : FALSE
 Value of frac :        9.0949470177292824E-013

Tests for x =  9.9999999999999995E-021
Using SameValue: TRUE
   Using frac  : FALSE
 Value of frac :        9.9999999999999995E-021
« Last Edit: May 15, 2018, 04:22:36 pm by Gammatester »

Thaddy

  • Hero Member
  • *****
  • Posts: 6732
Re: Testing if a float has a fractional part
« Reply #1 on: May 15, 2018, 04:31:29 pm »
Huh? Nonsense. You forget that the least significant values can not be expressed in decimal. The processors are not decimal but base 2 and derived, not base 10.
SameValue takes that into account and allows for it. You should make a small study on numerical systems to see where you are wrong and not focus on base 10.
Start with https://en.wikipedia.org/wiki/Numeral_system and click some more links....

Note you are right in theory given base 10, but CPU's don't work like that, so in practice there is always a small precision penalty on real numbers unless you use a special library to achieve higher (any) precision at the cost of speed. (included with FPC, btw).

Also note it is both enlightening and fun to play with numerical systems. My favorites are base 7, 11 and 13....
« Last Edit: May 15, 2018, 04:37:54 pm by Thaddy »
Ada's daddy wrote this:"Fools are my theme, let satire be my song."

Gammatester

  • Jr. Member
  • **
  • Posts: 57
Re: Testing if a float has a fractional part
« Reply #2 on: May 15, 2018, 04:52:01 pm »
Huh? Nonsense. You forget that the least significant values can not be expressed in decimal

Judging from the quality of your answer, I guess you are the same Thaddy which commented in my bug report https://bugs.freepascal.org/view.php?id=33635

If yes, than once again your comments are non-sense not my statements.

Do you agree that all non-zero doubles x with absolute values less than 1 have a fractional part identical to x? (This is not question about exact representation, take x = 2^(-70) which is exactly representable). So why should the test whether frac(x) is zero should return true.?

« Last Edit: May 15, 2018, 04:53:42 pm by Gammatester »

Blaazen

  • Hero Member
  • *****
  • Posts: 2606
  • POKE 54296,15
    • Eye-Candy Controls
Re: Testing if a float has a fractional part
« Reply #3 on: May 15, 2018, 07:23:43 pm »
I got different result in the first case:
  Using SameValue: FALSE

Lazarus 1.9.0 r57941M FPC 3.1.1 x86_64-linux-qt
Lazarus 2.1.0 r59015M FPC 3.3.1 r39660 x86_64-linux-qt Chakra, Qt 4.8.7/5.11.1, Plasma 5.13.4
Lazarus 1.8.2 r57369 FPC 3.0.4 i386-win32-win32/win64 Wine 3.14

Try Eye-Candy Controls: https://sourceforge.net/projects/eccontrols/files/

Gammatester

  • Jr. Member
  • **
  • Posts: 57
Re: Testing if a float has a fractional part
« Reply #4 on: May 15, 2018, 08:08:03 pm »
I got different result in the first case:
  Using SameValue: FALSE

Lazarus 1.9.0 r57941M FPC 3.1.1 x86_64-linux-qt
Thank you for the feedback.

Is this a 64-bit executable (or 32-bit)? I also get FALSE  for the first case if I build a 32-bit exe. If yes, then this is an example of non-portability of the SameValue test. (Compilers: 3.1.1-r20:38794 [2018/04/22] for x86_64 and 3.0.4 [2017/10/06] for x86_64 with Win7/64)

Blaazen

  • Hero Member
  • *****
  • Posts: 2606
  • POKE 54296,15
    • Eye-Candy Controls
Re: Testing if a float has a fractional part
« Reply #5 on: May 15, 2018, 08:33:31 pm »
Yes, a 64-bit executable.
Lazarus 2.1.0 r59015M FPC 3.3.1 r39660 x86_64-linux-qt Chakra, Qt 4.8.7/5.11.1, Plasma 5.13.4
Lazarus 1.8.2 r57369 FPC 3.0.4 i386-win32-win32/win64 Wine 3.14

Try Eye-Candy Controls: https://sourceforge.net/projects/eccontrols/files/

Mr.Madguy

  • Sr. Member
  • ****
  • Posts: 337
Re: Testing if a float has a fractional part
« Reply #6 on: May 16, 2018, 11:42:00 am »
Any float value (except 80-bit one - it's stored in "raw" form) is stored in following form:
<mantissa sign>(1).<mantissa>*2^(<exponent sign><exponent>)
All numbers are binary. So, there is no such thing, as "machine epsilon" here. If after shifting (1)<mantissa> left by <exponent sign><exponent> + 1 bits it's still non-zero - then this float value has fractional part.
« Last Edit: May 16, 2018, 11:59:14 am by Mr.Madguy »
DynamicData 2.0 Released! Many optimizations: 15% smaller sources, 10% smaller code! Improved scalability!
Developing version 2.1.
Crazy unit testing

Gammatester

  • Jr. Member
  • **
  • Posts: 57
Re: Testing if a float has a fractional part
« Reply #7 on: May 16, 2018, 12:19:45 pm »
All numbers are binary. So, there is no such things, as "machine epsilon" here.
See https://en.wikipedia.org/wiki/Machine_epsilon, definition b.

Any float value (except 80-bit one - it's stored in "raw" form) is stored in following form:
<mantissa sign>(1).<mantissa>*2^(<exponent sign><exponent>)... If after shifting (1)<mantissa> left by <exponent sign><exponent> bits it's still non-zero - then this float value has fractional part.
The <exponent> field is the biased exponent (https://en.wikipedia.org/wiki/Double-precision_floating-point_format), so shifting with this field will cause overflow in many cases.
 
Even if you shift with the unbiased exponent this is mostly wrong because all double precision numbers >= 2^52 have a zero fractional part (and I guess with fractional part you mean non-zero fractional part)
« Last Edit: May 16, 2018, 12:22:45 pm by Gammatester »

Mr.Madguy

  • Sr. Member
  • ****
  • Posts: 337
Re: Testing if a float has a fractional part
« Reply #8 on: May 16, 2018, 06:05:09 pm »
All numbers are binary. So, there is no such things, as "machine epsilon" here.
See https://en.wikipedia.org/wiki/Machine_epsilon, definition b.

Any float value (except 80-bit one - it's stored in "raw" form) is stored in following form:
<mantissa sign>(1).<mantissa>*2^(<exponent sign><exponent>)... If after shifting (1)<mantissa> left by <exponent sign><exponent> bits it's still non-zero - then this float value has fractional part.
The <exponent> field is the biased exponent (https://en.wikipedia.org/wiki/Double-precision_floating-point_format), so shifting with this field will cause overflow in many cases.
 
Even if you shift with the unbiased exponent this is mostly wrong because all double precision numbers >= 2^52 have a zero fractional part (and I guess with fractional part you mean non-zero fractional part)
Floating point value is exactly the same, as fixed point value. I.e. X bits go to integer part and Y=N-X, where N - is number of bits in mantissa, bits go fractional part. Only difference - point isn't fixed, i.e. we can set, how many bits go to integer part and how many to fractional one. Via exponent. And of course point can go out of bit field range - value is padded by zeros then. This just makes floating point values more flexible - number of bits, allocated for integer and fractional parts, depends on our task. And "floating point value has fractional part" = "fractional part has at least one bit and at least one bit in fractional part isn't zero". Shifting causes overflow? That's, what we actually need, because our goal is - to remove integer part.
« Last Edit: May 16, 2018, 06:14:03 pm by Mr.Madguy »
DynamicData 2.0 Released! Many optimizations: 15% smaller sources, 10% smaller code! Improved scalability!
Developing version 2.1.
Crazy unit testing

Gammatester

  • Jr. Member
  • **
  • Posts: 57
Re: Testing if a float has a fractional part
« Reply #9 on: May 16, 2018, 08:07:11 pm »
Floating point value is exactly the same, as fixed point value. I.e. X bits go to integer part and Y=N-X, where N - is number of bits in mantissa, bits go fractional part. Only difference - point isn't fixed, i.e. we can set, how many bits go to integer part and how many to fractional one.
Incorrect. You confuse fractional part with significand (or mantissa).  Frac(x) returns the non-integer part of x (see https://www.freepascal.org/docs-html/rtl/system/frac.html). Since there are only 52 explicit and one implied bits in the significand of a binary-64 double, all double precision numbers x > 2^52 have frac(x)=0, ie are integers (this feature is used in the new code for frac, https://svn.freepascal.org/cgi-bin/viewvc.cgi/trunk/rtl/x86_64/math.inc?r1=38856&r2=38903),  all x > 2^53 are even integers etc.

5432101234567890.5  -> frac(x) = 0, int(x) = 5432101234567890
5432101234567891.5  -> frac(x) = 0, int(x) = 5432101234567892
5432101234567891.25 -> frac(x) = 0, int(x) = 5432101234567891
9432101234567891.25 -> frac(x) = 0, int(x) = 9432101234567892

(Note the round to even). If you don't believe this, here is a test program (compile as 64-bit)
Code: Pascal  [Select]
  1. uses
  2.   math;
  3. const
  4.   x: array[0..3] of double = (5432101234567890.5, 5432101234567891.5,
  5.                               5432101234567891.25, 9432101234567891.25);
  6. var
  7.   i: integer;
  8. begin
  9.   for i:=0 to 3 do begin
  10.     writeln(x[i]:20, '   ', frac(x[i]):20, int(x[i]):30:5);
  11.   end;
  12. end.
with the output
Code: [Select]
D:\Work\DAMath>D:\FPC304\bin\i386-win32\ppcrossx64.exe xx2.pas
Free Pascal Compiler version 3.0.4 [2017/10/06] for x86_64
Copyright (c) 1993-2017 by Florian Klaempfl and others
Target OS: Win64 for x64
Compiling xx2.pas
Linking xx2.exe
12 lines compiled, 0.2 sec, 71680 bytes code, 4980 bytes data

D:\Work\DAMath>xx2.exe
 5.432101234568E+015    0.000000000000E+000        5432101234567890.00000
 5.432101234568E+015    0.000000000000E+000        5432101234567892.00000
 5.432101234568E+015    0.000000000000E+000        5432101234567891.00000
 9.432101234568E+015    0.000000000000E+000        9432101234567892.00000

Mr.Madguy

  • Sr. Member
  • ****
  • Posts: 337
Re: Testing if a float has a fractional part
« Reply #10 on: May 17, 2018, 05:05:03 am »
That's almost the same, as what I've said - I just tried to explain it in more simple fashion. Big numbers need point to shift right - more bits used for integer part. All bits are used at some point, yeah. What I tried to say - is that you can always examine binary representation of number. May be this method isn't cross-platform, but there is no better method, than this.
« Last Edit: May 17, 2018, 05:27:02 am by Mr.Madguy »
DynamicData 2.0 Released! Many optimizations: 15% smaller sources, 10% smaller code! Improved scalability!
Developing version 2.1.
Crazy unit testing

 

Recent

Get Lazarus at SourceForge.net. Fast, secure and Free Open Source software downloads Open Hub project report for Lazarus