Get a fraction from its decimal number - delphi

I am developing a program that solves a system of equations. When it gives me the results, it is like: "x1= 1,36842". I'd like to get the fraction of that "1,36842", so I wrote this code.
procedure TForm1.Button1Click(Sender: TObject);
var numero,s:string;
a,intpart,fracpart,frazfatta:double;
y,i,mcd,x,nume,denomin,R:integer;
begin
a:=StrToFloat(Edit1.Text); //get the value of a
IntPart := Trunc(a); // here I get the numerator and the denominator
FracPart := a-Trunc(a);
Edit2.Text:=FloatToStr(FracPart);
numero:='1';
for i:= 1 to (length(Edit2.Text)-2) do
begin
numero:=numero+'0';
end; //in this loop it creates a string that has many 0 as the length of the denominator
Edit3.text:=FloatToStr(IntPart);
y:=StrToInt(numero);
x:=StrToInt(Edit3.Text);
while y <> 0 do
begin
R:= x mod y;
x:=y;
y:=R;
end;
mcd:=x; //at the end of this loop I have the greatest common divisor
nume:= StrToInt(Edit3.Text) div mcd;
denomin:= StrToInt(numero) div mcd;
Memo1.Lines.Add('fraction: '+IntToStr(nume)+'/'+IntToStr(denomin));
end;
It doesn't work correctly because the fraction that it gives to me is wrong. Could anyone help me please?

Your code cannot work because you are using binary floating point. And binary floating point types cannot represent the decimal numbers that you are trying to represent. Representable binary floating point numbers are of the form s2e where s is the significand and e is the exponent. So, for example, you cannot represent 0.1 as a binary floating point value.
The most obvious solution is to perform the calculation using integer arithmetic. Don't call StrToFloat at all. Don't touch floating point arithmetic. Parse the input string yourself. Locate the decimal point. Use the number of digits that follow to work out the decimal scale. Strip off any leading or trailing zeros. And do the rest using integer arithmetic.
As an example, suppose the input is '2.79'. Convert that, by processing the text, into numerator and denominator variables
Numerator := 279;
Denominator := 100;
Obviously you'd have to code string parsing routines rather than use integer literals, but that is routine.
Finally, complete the problem by finding the gcd of these two integers.
The bottom line is that to represent and operate on decimal data you need a decimal algorithm. And that excludes binary floating point.

I recommend defining a function GreaterCommonDivisor function first (wiki reference)
This is going to be Java/C like code since I'm not familiar with Delphi
let
float x = inputnum // where inputnum is a float
// eg. x = 123.56
Then, multiplying
int n = 1;
while(decimalpart != 0){// or cast int and check if equal-> (int)x == x
x = x * 10;
decimalpart = x % 1;
// or a function getting the decimal part if the cast does work
n *= 10;
}
// running eg. x = 123.56 now x = 12356
// n = 100
Then you should have (float)x/n == inputnum at this point eg. (12356/100 == 123.56)
This mean you have a fraction that may not be simpified at this point. All you do now is implement and use the GCD function
int gcd = GreaterCommonDivisor(x, n);
// GreaterCommonDivisor(12356, 100) returns 4
// therefore for correct implementation gcd = 4
x /= gcd; // 12356 / 4 = 3089
n /= gcd; // 100 / 4 = 25
This should be quick and simple to implement, but:
Major Pitfalls:
Float must be terminating. For example expected value for 0.333333333333333333 won't be rounded to 1/3
Float * n <= max_int_value, otherwise there will be a overflow, there are work around this, but there may be another solutions more fitting to these larger numbers

Continued fractions can be used to find good rational approximations to real numbers. Here's an implementation in JavaScript, I'm sure it's trivial to port to Delphi:
function float2rat(x) {
var tolerance = 1.0E-6;
var h1=1; var h2=0;
var k1=0; var k2=1;
var b = x;
do {
var a = Math.floor(b);
var aux = h1; h1 = a*h1+h2; h2 = aux;
aux = k1; k1 = a*k1+k2; k2 = aux;
b = 1/(b-a);
} while (Math.abs(x-h1/k1) > x*tolerance);
return h1+"/"+k1;
}
For example, 1.36842 is converted into 26/19.
You can find a live demo and more information about this algorithm on my blog.

#Joni
I tried 1/2 and the result was a "division by zero" error;
I correct the loop adding:
if b - a = 0 then BREAK;
To avoid
b:= 1 / (b - a);

Related

A working function but having trouble with a particular float value

This function takes a float then spits out the two integers for the decimal value. At least that was the intention
let flr (x:float) = float(int(x))
let f x =
let r y = let x = x * y in x = flr(x)
let rec f y =
if r(y)
then x*y,y
else f(y+1.0)
f 1.0
f 0.2;;
val it: float * float = (1.0, 5.0)
f 3.14;;
val it: float * float = (157.0, 50.0)
Here is an example where the integers, er will be integers eventually rather, have not been "simplified"
f 0.14;;
val it: float * float = (35.0, 250.0)
Checking the fractional part to be less than .01, as opposed to equaling exactly zero, got around this issue but I don't really like that solution. So I set it back to what you see in the code above. I am using the function below for some of the values that do not simplify though:
let g (x,y) =
let rec f k =
if x/k = flr(x/k)
then g(k)
else f(k-1.0)
and g k =
if y/k = flr(y/k)
then x/k,y/k
else f(k-1.0)
if x < y then f x else f y
Anyway, the main issue is this value:
3.142857143
Homeboy just keeps grinding without stack errors and I'm not sure what I've ran into here. Any clarity would be awesome! Thanks y'all.
Your algorithm is trying to find a rational number to represent a decimal number (represented as a floating point number).
For any input x, you are looking for a number represented as p/q such that x=p/q and you do this by incrementing q, starting from 1 and checking if you can find an integer p to make this work.
This works fine for numbers that have a nice rational representation like 0.2, but it does not work great for numbers like 3.142857 that do not have a simpler rational representation. For 3.142857, you will just keep iterating until you reach 3142857/1000000 (which is technically correct, but not very helpful).
As mentioned in the comments, there are issues caused by the fact that floating-point numbers cannot be precisely compared, but also, iterating like this for 3.142857143 might just take too long.
You can look up better algorithms for finding a rational number for a given decimal. You could also see if you can accept some margin of error. If you do not need a completely precise solution, you could for example change your r test function to something like:
let r y =
let x = x * y
x < flr(x) + 0.0001 && x > flr(x) + 0.0001
This will not give you exactly the same number, but it will likely find a solution that is good enough.

Dart double division precision

What is the correct way to perform this operation?
399.9 / 100
What I would expect to see is
3.999
but the result is
3.9989999999999997
The result you see is correct, it's just not what you want.
Doubles are not precise values. The double you get by writing 399.9 is actually the precise value.
399.8999999999999772626324556767940521240234375
That's the closest available double to the exact value 399.9. Any other double is at least as far away from 399.9 as that.
Then you divide by 100. Again, the result is not precise, but the closest double has the exact value
3.99899999999999966604491419275291264057159423828125
That differs from what you would get by writing 3.999, which is the exact value:
3.999000000000000110134124042815528810024261474609375
At every step, the double operations have minimized the error, but because you are doing multiple steps, the final result diverges from the double closest to the mathematical result.
What you need to do depends on what your actual requirements are.
If you want to always calculate with two significant digits, then I'd just multiply my numbers with 100 and do all the operations as integer operations, until the very last division by 100.
If you have an intermediate result and wants to round it to two digits, I'd do what Fy Z1K says:
result = (result * 100).round() / 100;
import 'dart:math';
double roundDouble(double value, int places){
double mod = pow(10.0, places);
return ((value * mod).round().toDouble() / mod);
}
then you would basically get
double num1 = roundDouble(12.3412, 2);
// 12.34
double num2 = roundDouble(12.5668, 2);
// 12.57
double num3 = roundDouble(-12.3412, 2);
// -12.34
double num4 = roundDouble(-12.3456, 2);
// -12.35
To make decimal operations you can use the decimal package.
final d = Decimal.parse;
print(d('399.9') / d('100')); // => 3.999

Is there a compiler setting to control how floating point literals are typed in Delphi?

While the the case for e works by default I would like to change the default casting of the literal 0.1 to allow the r to work without any code modifications. Is this possible through a compiler option, compiler directive, or anything else?
procedure Test;
var
s : Single;
r : Real;
d : Double;
e : Extended;
begin
s := 0.1;
if (s = 0.1) then ShowMessage('s matched'); // fail
r := 0.1;
if (r = 0.1) then ShowMessage('r matched'); // fail
d := 0.1;
if (d = 0.1) then ShowMessage('d matched'); // fail
e := 0.1;
if (e = 0.1) then ShowMessage('e matched'); // pass
end;
There is no compiler switch that does what you wish. The issue is that a floating point literal is represented as a 10 byte extended precision value by the 32 bit Windows compiler. And because 0.1 is not exactly representable, that is not equal to the 8 byte double precision representation of 0.1.
The documentation says:
If constantExpression is a real, its type is Extended.
You can however achieve the desired result by using a typed constant. For example:
const
TenthDouble: Double = 0.1;
var
d: Double;
....
d := 0.1;
if d = TenthDouble then
....
By using a typed constant we are able to force the compiler to make the constant be the 8 byte double precision representation of 0.1.
The problem is not the casting, but the comparison itself, and how floating points are encoded. See e.g. this blog article, which deals explicitely with the 0.1 value. In short, this 0.1 value is not encoded as 0.1 but as 0.100000001490116119384765625 (single) or 0.10000000000000000555111512312578270211815834045 (double) in the IEEE-754 format...
In fact, the following line
if (s = 0.1) then ShowMessage('s matched'); // fail
is compiled (at least under x87 i.e. Delphi 32-bit) as
s := 0.1;
e := 0.1;
if s=e then
writeln('ok');
And in the IEEE standard encoding, 0.1 is not stored the same as extended or single precision:
s := 0.1;
e := 0.1;
writeln('s=',BinToHex(#s, SizeOf(s)));
writeln('e=',BinToHex(#e, SizeOf(e)));
//s=CDCCCC3D
//e=CDCCCCCCCCCCCCCCFB3F
Whereas for 0.5 there is no rounding problem:
s := 0.5;
e := 0.5;
writeln('s=',BinToHex(#s, SizeOf(s)));
writeln('e=',BinToHex(#e, SizeOf(e)));
if s=e then
writeln(ok); // it works!
// s=0000003F
// e=0000000000000080FE3F
You can force the comparison as such:
var s, s2: single;
s := 0.1;
s2 := 0.1;
if s = s2 then
writeln('ok');
But anyway, to properly compare float values, the SameValue() methods of the Math.pas unit may be used, with an appropriate epsilon.
Not that I know. The x87 coprocessor default works in extended, so the compiler adapts.
Note that if you compile this program for 64-bit r,d and e match (probably because on 64-bit real=double=extended). 64-bit code does not use x87 for this but SSE2 as per 64-bit ABI.

How to measure distance between two coordinates with less calc possible?

I'm trying to make measures using the less CPU possible, so I'm using a constant multiplicator to get to a value in meters like this:
lat1,long1 = Coordinate 1
lat2,long2 = Coordinate 2
DistV := abs(lat1-lat2); // Get a positive vertical value
DistH := abs(lon1-long2); // Get a positive horizontal value
DistGPS := sqrt(sqr(DistV) + sqr(DistH)); // Get a diagonal value
DistMeters := DistGPS*(111120*0.946); // 111120*0.946 = Constant multiplicator to meters
However, the values calculated are going to be added to the previous measures, making it necessary to be accurate. Does anyone know a better way for doing it?
To measure a distance more accurately, you can use Haversine formula, as written in comments. Here you can see formula and JavaScript implementation:
var R = 6371e3; // metres
var φ1 = lat1.toRadians();
var φ2 = lat2.toRadians();
var Δφ = (lat2-lat1).toRadians();
var Δλ = (lon2-lon1).toRadians();
var a = Math.sin(Δφ/2) * Math.sin(Δφ/2) +
Math.cos(φ1) * Math.cos(φ2) *
Math.sin(Δλ/2) * Math.sin(Δλ/2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
var d = R * c;
If you think that rough approximation is quite good for you purposes, you can make it more precise accounting for distance contraction along meridian (instead of common constant for both coordinates):
DistV := abs(lat1-lat2); // Get a positive vertical value
DistH := abs(lon1-long2); // Get a positive horizontal value
DistH := DistH * Cos((lat1+lat2) / 2); // Use average latitude
DistGPS := sqrt(sqr(DistV) + sqr(DistH)); // Get a diagonal value
DistMeters := DistGPS*111120; //to meters

Rounding when [int] = [float] + [int] (Obj-C, iOS?)

In this case:
float a = 0.99999f;
int b = 1000;
int c = a + b;
In result c = 1001. I discovered that it happens because b is converted to float (specific for iOS), then a + b doesn't have enough precision for 1000.9999 and (why?) is rounded to higher value. If a is 0.999f we get c = 1000 - theoretically correct behavior.
So my question is why float number is rounded to higher value? Where this behavior (or convention) is described?
I tested this on iPhone Simulator, Apple LLVM 4.2 compiler.
In int c = a + b, the integer b is converted to a float first, then 2 floating point
numbers are added, and the result is truncated to an integer.
The default floating point rounding mode is FE_TONEAREST, which means that the result
of the addition
0.99999f + 1000f
is the nearest number that can be represented as a float, and that is the number 1001f. This float is then truncated to the integer c = 1001.
If you change the rounding mode
#include <fenv.h>
fesetround(FE_DOWNWARD);
then the result of the addition is rounded downward (approximately 1000.99993f) and you would get c = 1000.
The reason is that when you add 1000 you get 8 total decimal digits of precision, but IEEE float is only supports 7 digits.

Resources