glen herrmannsfeldt said:
I pick the points (0,0) and (512,997) slightly easier to see as
integers, but you can shift the binary point over and make them floating
point. Find any point on the segment between them.
I wrote a little program which uses rational numbers and floating point
numbers and shows the difference.
The rational number version is able to perfectly calculate the point.
The floating point version fails miserably at it
The rational number version is about 20 times slower than the floating point
version
program started
(Rational) PointX in rational notation: 768/5
(Rational) PointY in rational notation: 2991/10
(Rational) PointX in real notation: 153.6000000000000000
(Rational) PointY in real notation: 299.1000000000000000
(Rational) ticks: 282
(Rational) time (in seconds): 0.0000787809623849
(Floating point) PointX: 153.5999999999999940
(Floating point) PointY: 299.0999999999999660
(Floating point) ticks: 14
(Floating point) time (in seconds): 0.0000039111116078
press enter to exit
Here is the source code. Thanks to delphi fundamentals for the Trational
class etc =D
Now for the final question
Can you still find a point which would in theory be on the line segment but
would still be mis-calculated by the rational number version ?
program TestRationalNumbers;
{$APPTYPE CONSOLE}
uses
SysUtils,
cUtils in 'Utils\cUtils.pas',
cStrings in 'Utils\cStrings.pas',
cRational in 'Maths\cRational.pas',
cMaths in 'Maths\cMaths.pas',
Windows;
// rational number versions:
procedure calc_perpendicular( var x, y : Trational );
var
temp_x, temp_y : Trational;
begin
temp_x := Trational.Create;
temp_y := Trational.Create;
// temp_x := -y;
temp_x.Assign( y );
temp_x.Negate;
// temp_y := x;
temp_y.Assign( x );
// x := temp_x;
// y := temp_y;
x.Assign( temp_x );
y.Assign( temp_y );
temp_x.Destroy;
temp_y.Destroy;
end;
procedure calc_normalize( var x, y : Trational );
var
temp_squared_x : Trational;
temp_squared_y : Trational;
temp_length : Trational;
begin
temp_squared_x := Trational.Create;
temp_squared_y := Trational.Create;
temp_length := Trational.Create;
// temp_length := sqrt( (x*x) + (y*y) );
temp_squared_x.Assign( x );
temp_squared_y.Assign( y );
temp_squared_x.Sqr;
temp_squared_y.Sqr;
temp_length.Assign( temp_squared_x );
temp_length.Add( temp_squared_y );
temp_length.Sqrt;
// if temp_length<>0 then
if not temp_length.IsZero then
begin
// x := x / temp_length;
// y := y / temp_length;
x.Divide( temp_length );
y.Divide( temp_length );
end;
temp_squared_x.Destroy;
temp_squared_y.Destroy;
temp_length.Destroy;
end;
procedure calc_normal( _x1,_y1, _x2,_y2 : Trational; var _normal_x,
_normal_y : Trational );
begin
// _normal_x := _x2-_x1;
// _normal_y := _y2-_y1;
_normal_x.Assign( _x2 );
_normal_y.Assign( _y2 );
_normal_x.Subtract( _x1 );
_normal_y.Subtract( _y1 );
// calculate perpendicular "vector".
calc_perpendicular( _normal_x, _normal_y );
// divide vector by it's own length there by creating a "normal"
calc_normalize( _normal_x, _normal_y );
end;
// original double versions
// using flawed floating point shit
// helper procedure to generate points on or near line segments
procedure GeneratePointForLineSegment( StartX, StartY,
EndX, EndY : Trational;
DistanceFromStart : Trational; // distance from start
// < 0 is before start
// 0 is on start
// 0.5 is on center
// 1 is one end
// > 1 is after end
DistanceFromLine : Trational; // closest distance from line
// follows the normal
// positive is left side of line
// zero is on line
// negative is right side of line
var px, py : Trational );
var
NX, NY : Trational;
begin
NX := Trational.Create;
NY := Trational.Create;
// theory of method1
// PX := StartX + DistanceFromStart * (EndX - StartX);
// PY := StartY + DistanceFromStart * (EndY - StartY);
PX.Assign( EndX );
PX.Subtract( StartX );
PX.Multiply( DistanceFromStart );
PX.Add( StartX );
PY.Assign( EndY );
PY.Subtract( StartY );
PY.Multiply( DistanceFromStart );
PY.Add( StartY );
// create a perpendicular point... we do this by calculating a normal and
multiplieing
// this with the distance from the line and adding this to px and py
// calculate normilized normal for line
calc_normal( StartX,StartY, EndX,EndY, NX,NY );
// offset the point along the normal with distance from line
// PX := PX + NX * DistanceFromLine;
// PY := PY + NY * DistanceFromLine;
NX.Multiply( DistanceFromLine );
NY.Multiply( DistanceFromLine );
PX.Add( NX );
PY.Add( NY );
NX.Destroy;
NY.Destroy;
end;
procedure org_calc_perpendicular( var x, y : double );
var
temp_x, temp_y : double;
begin
temp_x := -y;
temp_y := x;
x := temp_x;
y := temp_y;
end;
procedure org_calc_normalize( var x, y : double );
var
temp_length : double;
begin
temp_length := sqrt( (x*x) + (y*y) );
if temp_length<>0 then
begin
x := x / temp_length;
y := y / temp_length;
end;
end;
procedure org_calc_normal( _x1,_y1, _x2,_y2 : double; var _normal_x,
_normal_y : double );
begin
_normal_x := _x2-_x1;
_normal_y := _y2-_y1;
// calculate perpendicular "vector".
org_calc_perpendicular( _normal_x, _normal_y );
// divide vector by it's own length there by creating a "normal"
org_calc_normalize( _normal_x, _normal_y );
end;
// helper procedure to generate points on or near line segments
procedure org_GeneratePointForLineSegment( StartX, StartY,
EndX, EndY : double;
DistanceFromStart : double; // distance from start
// < 0 is before start
// 0 is on start
// 0.5 is on center
// 1 is one end
// > 1 is after end
DistanceFromLine : double; // closest distance from line
// follows the normal
// positive is left side of line
// zero is on line
// negative is right side of line
var px, py : double );
var
NX, NY : double;
begin
// theory of method1
PX := StartX + DistanceFromStart * (EndX - StartX);
PY := StartY + DistanceFromStart * (EndY - StartY);
// create a perpendicular point... we do this by calculating a normal and
multiplieing
// this with the distance from the line and adding this to px and py
// calculate normilized normal for line
org_calc_normal( StartX,StartY, EndX,EndY, NX,NY );
// offset the point along the normal with distance from line
PX := PX + NX * DistanceFromLine;
PY := PY + NY * DistanceFromLine;
end;
var
vHighPerformanceCounterFrequency : int64;
vCounter1 : int64;
vCounter2 : int64;
vStartX,
vStartY,
vEndX,
vEndY,
vPointX,
vPointY,
vDistanceFromStart,
vDistanceFromLine : Trational;
vOrgStartX,
vOrgStartY,
vOrgEndX,
vOrgEndY,
vOrgPointX,
vOrgPointY,
vOrgDistanceFromStart,
vOrgDistanceFromLine : double;
vMeasurementsEnabled : boolean;
vRationalTicks : int64;
vRationalTime : double;
vFloatingPointTicks : int64;
vFloatingPointTime : double;
begin
writeln('program started');
vMeasurementsEnabled := true;;
vRationalTicks := 0;
vRationalTime := 0;
vFloatingPointTicks := 0;
vFloatingPointTime := 0;
if vMeasurementsEnabled then
begin
if not QueryPerformanceFrequency( vHighPerformanceCounterFrequency ) then
begin
writeln('High performance counter not present, speed measurements
disabled');
vMeasurementsEnabled := false;
end;
end;
vStartX := Trational.Create;
vStartY := Trational.Create;
vEndX := Trational.Create;
vEndY := Trational.Create;
vPointX := Trational.Create;
vPointY := Trational.Create;
vDistanceFromStart := Trational.Create;
vDistanceFromLine := Trational.Create;
vStartX.Assign( 0, 1 );
vStartY.Assign( 0, 1 );
vEndX.Assign( 512, 1 );
vEndY.Assign( 997, 1 );
vDistanceFromStart.Assign( 3, 10 );
vDistanceFromLine.Assign( 0, 1 );
if vMeasurementsEnabled then
begin
QueryPerformanceCounter( vCounter1 );
end;
GeneratePointForLineSegment(
vStartX, vStartY,
vEndX, vEndY,
vDistanceFromStart,
vDistanceFromLine,
vPointX, vPointY );
if vMeasurementsEnabled then
begin
QueryPerformanceCounter( vCounter2 );
vRationalTicks := vCounter2-vCounter1;
vRationalTime := ( (vCounter2-vCounter1)/
vHighPerformanceCounterFrequency );
end;
writeln;
writeln( '(Rational) PointX in rational notation: ', vPointX.AsString );
writeln( '(Rational) PointY in rational notation: ', vPointY.AsString );
writeln;
writeln( '(Rational) PointX in real notation: ', vPointX.AsReal : 16 :
16 );
writeln( '(Rational) PointY in real notation: ', vPointY.AsReal : 16 :
16 );
if vMeasurementsEnabled then
begin
writeln('(Rational) ticks: ', vRationalTicks );
writeln('(Rational) time (in seconds): ', vRationalTime : 16 : 16 );
end;
vOrgStartX := 0.0;
vOrgStartY := 0.0;
vOrgEndX := 512.0;
vOrgEndY := 997.0;
vOrgDistanceFromStart := 0.3;
vOrgDistanceFromLine := 0.0;
if vMeasurementsEnabled then
begin
QueryPerformanceCounter( vCounter1 );
end;
org_GeneratePointForLineSegment(
vOrgStartX, vOrgStartY,
vOrgEndX, vOrgEndY,
vOrgDistanceFromStart,
vOrgDistanceFromLine,
vOrgPointX, vOrgPointY );
if vMeasurementsEnabled then
begin
QueryPerformanceCounter( vCounter2 );
vFloatingPointTicks := vCounter2-vCounter1;
vFloatingPointTime := ( (vCounter2-vCounter1)/
vHighPerformanceCounterFrequency );
end;
writeln;
writeln( '(Floating point) PointX: ', vOrgPointX : 16 : 16 );
writeln( '(Floating point) PointY: ', vOrgPointY : 16 : 16 );
if vMeasurementsEnabled then
begin
writeln('(Floating point) ticks: ', vFloatingPointTicks );
writeln('(Floating point) time (in seconds): ', vFloatingPointTime : 16 :
16 );
end;
vStartX.Destroy;
vStartY.Destroy;
vEndX.Destroy;
vEndY.Destroy;
vPointX.Destroy;
vPointY.Destroy;
vDistanceFromStart.Destroy;
vDistanceFromLine.Destroy;
writeln;
writeln('press enter to exit');
readln;
writeln('program finished');
end.
Bye,
Skybuck.