Mathematically speaking, there is an infinete count of floating point numbers. But in a computer there is only a finite number of bytes to represent these numbers; therefore, floating point values are normally rounded to the closest value which can be expressed exactly as a floating point value. Note that floating points values are stored in a binary system, therefore values which look to be exact for our eyes (like 1.4) are not exact for the computer at all.
The string conversion routines are highly sophisticated and round off many of the floating point inaccuracies. But when you go to a console program and simply "WriteLn(frac(i/10))" then you'll get "3.99999999999999999978E-0001". This shows immediately that the result of frac(14/10) cannot be expressed exactly as a floating point number. Therefore, when comparing floating point number NEVER use the '=' operator, the function SameValue in unit math accepts a tolerance parameter and is much more reliable:
function SameValue(const A, B: Double): Boolean; overload;
function SameValue(const A, B: Double; Epsilon: Double): Boolean; overload;
// more overloads for extended and single
When you don't specify the tolerance parameter like in the first call above, a relative tolerance of 1E-12 is used for double, and 1E-16 for extended and 1E-4 for single. This is ok for most cases. But sometimes, in particular after heavy number crunching using numerical calculations with limited accuracy, a higher value must be specified (note that the Epsilon specified is the absolute tolerance).
program Project1;
uses
math;
var
i: Integer;
begin
i := 14;
WriteLn(frac(i/10));
WriteLn(SameValue(frac(i/10), 0.4));
WriteLn(SameValue(frac(i/10), 0.4, 1e-9));
ReadLn;
end.