How can i find out which number is closer? say my value is "1" and i have two var, A:= 1.6 and b:=1.001
currently looking at a few numbers and taking a 0.1% +/- difference and a +/- 0.6 difference.. i just need to see which answer is closer to the starting value.. code so far..
Also nothing to big, the code is just to stop me from doing them all manually :D
procedure TForm1.Button1Click(Sender: TObject);
var
winlimit,test6high,test6low,test6,test1high,test1low,test1,value: double;
begin
value := 1.0;
while value < 1048567 do
begin
test6high := value + 0.6 ;
test6low := value - 0.6 ;
test1high := (-0.1 * value)/100;
test1high := value - test1high;
test1low := (0.1 * value)/100;
test1low := value - test1low;
memo1.Lines.Add('value is '+floattostr(value)+': 1% High:'+floattostr(Test1high)+' 1% Low:'+floattostr(Test1low));
memo1.Lines.Add('value is '+floattostr(value)+': 0.6 +/- '+floattostr(Test6high)+' 0.6 Low:'+floattostr(Test6low));
memo1.Lines.Add(' ');
value := value*2;
end
end;
I think you mean a function like this:
function ClosestTo(const Target, Value1, Value2: Double): Double;
begin
if abs(Target-Value1)<abs(Target-Value2) then
Result := Value1
else
Result := Value2;
end;
If you use IfThen from the Math unit you can write it more concisely:
function ClosestTo(const Target, Value1, Value2: Double): Double;
begin
Result := IfThen(abs(Target-Value1)<abs(Target-Value2), Value1, Value2);
end;
Related
The code below draws a logarithmic grid with DrawGrid(). It seems the vertical lines are ok.
When I use the function SetPositionHzValue() the resulting position seems ok (it uses the same logic as the DrawGrid() and seems to match the grid).
But how to convert this 0 - 1.0 normalized value, that uses the display width linearly, to an actual Hz value? Why is the function GetPositionsHzValue() wrong?
To complicate things, the display has a start frequency (20 Hz in this case) and an end frequency (44100 Hz in this case).
procedure TAudioBezierCurves.DrawGrid(Bitmap32: TBitmap32);
var
GridPosition: Integer;
GridPositionF: Double;
i: Integer;
Base: Double;
LogOffsetValue: Double;
LogMaxValue: Double;
begin
GridPosition := 0;
Base := 1;
if GridFrequencyMin = 0 then begin
LogOffsetValue := 0;
end else begin
LogOffsetValue := Log10(GridFrequencyMin);
end;
LogMaxValue := Log10(GridFrequencyMax) - LogOffsetValue;
repeat
for i := 2 to 10 do begin
if Base * i < GridFrequencyMin then begin
Continue;
end;
//* This gives the % value relative to the total scale
GridPositionF := (Log10(Base * i) - LogOffsetValue) / LogMaxValue;
GridPositionF := GridPositionF * Bitmap32.Width;
GridPosition := Trunc(GridPositionF);
Bitmap32.VertLineS(GridPosition, 0, Bitmap32.Height - 1, GridColor);
end;
Base := Base * 10;
until GridPosition > Bitmap32.Width;
end;
procedure TAudioBezierCurve.SetPositionHzValue(AValue: Double);
var
LogOffsetValue: Double;
LogMaxValue: Double;
begin
if AValue = 0 then begin
Self.Position := 0;
end else begin
if Parent.GridFrequencyMin = 0 then begin
LogOffsetValue := 0;
end else begin
LogOffsetValue := Log10(Parent.GridFrequencyMin);
end;
LogMaxValue := Log10(Parent.GridFrequencyMax) - LogOffsetValue;
//* This gives the % value relative to the total scale
AValue := (Log10(AValue) - LogOffsetValue) / LogMaxValue;
Self.Position := AValue;
end;
end;
function TAudioBezierCurve.GetPositionsHzValue: Double;
var
AValue: Double;
begin
AValue := Self.Position;
AValue := Power(AValue, 2);
Result := AValue * (Parent.GridFrequencyMax);
Result := Result - (AValue * Parent.GridFrequencyMin) + Parent.GridFrequencyMin;
end;
EDIT: Ok, almost ok now. So it seems the correct function is:
AValue := Power(AValue, 10);
But still not perfect. Changing the display range to min. 0 to 44100, for simplicity, results that setting to the upper value 44100 is ok, the function GetPositionsHzValue() report 41100. But calling setting the position value to 20, GetPositionsHzValue() reports 0.
Trying to decrement the position all is fine until 44085, but 44084 value is reported as 44085 and this difference increases with smaller values. Going from lower values, it's 0 until 39, 40 results 1.
In function GetPositionsHzValue, line "AValue := Power(AValue, 2);" where does the value of "AValue" come from?
Maybe you should do something like you did in "SetPositionHzValue(AValue: Double);". AValue should be a parameter, not a local variable.
Found the solution, it should be:
function TAudioBezierCurve.GetPositionsHzValue: Double;
var
AValue: Double;
begin
AValue := Self.Position;
AValue := AValue * Log10(Parent.GridFrequencyMax) + (Log10(Parent.GridFrequencyMin) * (1 - AValue)); //* Results "min." at 0
Result := Power(10, AValue);
end;
If possible, I would like to avoid converting a Currency to Extended (and possible losing precision) in code similar to the following:
function CurrencyToNumeric(aCurrency: Currency; aScale: Integer): Int64;
const
scales: array [-{18}5..-1] of int64 = (100000, 10000, 1000, 100, 10);
var
aCurrencyAsInt64: Int64 absolute aCurrency;
begin
if aScale = -4 then
Result := aCurrencyAsInt64
else
Result := Round(aCurrency * scales[aScale]); // currency -> extended -> integer
end;
Is that possible?
I believe that you are looking for a function like this:
function CurrencyToNumeric(aCurrency: Currency; aScale: Integer): int64;
var
aCurrencyAsInt64: int64 absolute aCurrency;
i, factor, rem: Integer;
begin
if aScale <= -4 then begin
factor := 1;
for i := -4 downto aScale+1 do begin
factor := factor * 10;
end;
Result := aCurrencyAsInt64 * factor;
end else begin
factor := 1;
for i := -4 to aScale-1 do begin
factor := factor * 10;
end;
Result := aCurrencyAsInt64 div factor;
rem := aCurrencyAsInt64 mod factor;
if rem>=factor div 2 then begin
inc(Result);
end;
end;
end;
This part of the code
if rem>=factor div 2 then begin
inc(Result);
end;
implements the rounding policy. You may very well wish to make a different choice. Modify this code to do so, it should be obvious how to go about that.
However, I am also not convinced that the version in the question is broken. Do you have any example input where it fails? On the other hand, avoiding converting to binary floating point for a fixed point decimal type does feel sensible. Now, if only Embarcadero had implemented this darn type without resorting to using floating point operations.
Thanks to David's answer, I ended up with following implementation, which is not only float-free but also faster than function from the question.
function CurrencyToNumeric(Value: Currency; Scale: Integer): Int64;
const
factors: array [-4..-1] of Int64 = (10000, 1000, 100, 10);
var
factor: Integer;
ValueAsInt64: Int64 absolute Value;
begin
if Scale = -4 then
Result := ValueAsInt64
else if Scale < -4 then
Result := ValueAsInt64 * factors[4 + Scale]
else begin
factor := factors[-(4 + Scale)];
Result := ValueAsInt64 div factor;
if ValueAsInt64 mod factor >= factor div 2 then Inc(Result);
end;
end;
I have created a dynamic array, and have passed values to it. Is there a shortcut for finding mean of dynamic array.
var
TheMin, TheMax: Integer;
x: array of Integer; //Dynamic array declaration
....
TheMin := MinIntValue(x);//I am able to retrieve the minium value of the dynamic array
TheMax := MaxIntValue(x);//I am able to retrieve the maximum value of the dynamic array
Is there a other way to get mean using Math library.
It is very easy to write such a function.
function Mean(const Data: array of Integer): Double; overload;
var
i: Integer;
begin
Result := 0.0;
for i := low(Data) to high(Data) do
Result := Result + Data[i];
Result := Result / Length(Data);
end;
I overloaded this so that it could sit alongside the same named functions in the Math unit.
If you wish to use built in library code you can use SumInt from the Math unit:
TheMean := SumInt(x) / Length(x);
SumInt performs the summation using an Integer accumulator. This is probably faster than the bespoke function that uses a floating point accumulator. However, an Integer accumulator is potentially subject to overflow which may be off-putting. On the other hand, an Integer accumulator is potentially more accurate than a floating point accumulator. Depending on your usage requirements these issues may be important to you.
In bother cases, if the input array is of length zero a runtime floating point divide by zero error will be raised.
If the array has additions or deletions, recalculating the average from scratch can get rather time consuming.
In that case it may be worthwhile to calculate a running average instead.
function RecalcAverage(OldAverage: double; const OldArray, Additions, Deletions: TIntArray): double; overload;
var
i: integer;
begin
i:= Length(OldArray) + Length(Additions) - Length(Deletions);
WeighingFactor := 1 / i;
Result:= OldAverage;
for i:= 0 to Length(Deletions) -1 do begin
Result:= Result - (Deletions[i] * WeighingFactor);
end;
for i:= 0 to Length(Additions) -1 do begin
Result:= Result + (Additions[i] * WeighingFactor);
end;
end;
If you have a running sum handy, you can avoid the rounding errors and calculate an exact average.
function RecalcAverage(var RunningTotal: Int64; const OldArray, Additions, Deletions: TIntArray): double; overload;
var
i: integer;
begin
for i:= 0 to Length(Deletions) -1 do begin
RunningTotal:= RunningTotal - Deletions[i];
end;
for i:= 0 to Length(Additions) -1 do begin
RunningTotal:= RunningTotal + Additions[i];
end;
Result:= RunningTotal / (Length(OldArray) + Length(Additions) - Length(Deletions));
end;
If performance is an issue, it would make much more sense to calculate all the needed values in a single loop.
type
TStats = record
MaxVal: integer;
MinVal: integer;
Average: double;
end;
function CalcStats(const input: TIntArray): TStats;
var
MinVal, MaxVal: integer;
Total: Int64;
i: integer;
begin
Assert(Length(input) > 0);
MinVal:= input[0];
MaxVal:= MinVal;
Total:= MinVal;
for i:= 1 to Length(input) -1 do begin
MinVal:= Min(MinVal, input[i]);
MaxVal:= Max(MinVal, input[i]);
Total:= Total + input[i];
end;
Result.MinVal:= MinVal;
Result.MaxVal:= MaxVal;
Result.Average:= Total / Length(input);
end;
I have the following function which I'm led to believe should round time to nearest 15 minutes.
function TdmData.RoundTime(T: TTime): TTime;
var h, m, s, ms : Word;
begin
DecodeTime(T, h, m, s, ms);
m := (m div 15) * 15;
s := 0;
Result := EncodeTime(h, m, s, ms);
end;
To test the function I have put a tbutton and a tedit on a form and at the click of the button I do:
begin
Edit1.Text := RoundTime('12:08:27');
end;
I get an error when compiling : 'Incompatible types TTime and string'
Any help with this would be great.
Thanks,
The error which causes the compilation failure is that you are passing a string to a function which needs a TTime as a parameter.
Once this is fixed, Edit1.Text needs a string type but your function returns TTime.
Using StrToTime and TimeToStr you can obtain the desired conversion from and to a string type.
Your function can be called like this:
begin
Edit1.Text := TimeToStr(RoundTime(StrToTime('12:08:27'));
end;
Stealing the gabr user's answer - In Delphi: How do I round a TDateTime to closest second, minute, five-minute etc? - you can obtain a date rounded to an arbitrary nearest value assigned to the interval parameter:
function RoundToNearest(time, interval: TDateTime): TDateTime;
var
time_sec, int_sec, rounded_sec: int64;
begin
time_sec := Round(time * SecsPerDay);
int_sec := Round(interval * SecsPerDay);
rounded_sec := (time_sec div int_sec) * int_sec;
if ((rounded_sec + int_sec - time_sec) - (time_sec - rounded_sec)) > 0 then
rounded_sec := rounded_sec + time_sec + int_sec;
Result := rounded_sec / SecsPerDay;
end;
begin
Edit1.Text := TimeToStr(RoundToNearest(StrToTime('12:08:27'), StrToTime('0:0:15')));
end;
I'm looking for a function somewhere in Delphi XE2 similar to Inc() which allows me to add/subtract a number of degrees from a current number of degrees and result in the new degrees. For example, if I have a point currently at 5 degrees around a circle, and I want to subtract 10, I should not get -5 degrees, but rather 355 (360 - 5). Same as adding past 360 - it should go back to 0 when it reaches 360.
Is there anything like this already in Delphi so I don't have to re-write it? Perhaps in the Math unit?
uses
System.SysUtils,Math;
Function WrapAngle( angle : Double) : Double;
Const
modAngle : Double = 360.0;
begin
Result := angle - modAngle*Floor(angle/modAngle);
end;
begin
WriteLn(FloatToStr(WrapAngle(-5)));
WriteLn(FloatToStr(WrapAngle(5-720)));
WriteLn(FloatToStr(WrapAngle(360)));
ReadLn;
end.
Produces result:
355
5
0
Update:
As #Giel found, in XE3 there is a new function DegNormalize() which does the job. Even about 25% faster. The trick is to replace the Floor() call with an Int() instead, and if the result is negative, add modAngle to the result.
function WrapAngle(Value: Integer): Integer;
begin
Result := Value mod 360;
if Result < 0 then
Inc(Result, 360);
end;
The code I use to perform this task is:
function PosFrac(x: Double): Double;
(* PosFrac(1.2)=0.2 and PosFrac(-1.2)=0.8. *)
begin
Result := Frac(x); (* Frac(x)=x-Int(x) *)
if Result<0.0 then begin
Result := 1.0+Result;
end;
end;
function ModR(const x, y: Double): Double;
(* ModR(1.2,1)=0.2 and ModR(-1.2,1)=0.8 *)
var
absy: Double;
begin
if y=0.0 then begin
Result := 0.0;
end else begin
absy := abs(y);
Result := PosFrac(x/absy)*absy;
end;
end;
function Mod360(const x: Double): Double;
begin
Result := ModR(x, 360.0);
end;
This code will bring all angles into the range 0 to 360. For example:
Writeln(Round(Mod360(5-10)));
Writeln(Round(Mod360(5-360)));
Writeln(Round(Mod360(5-720)));
Writeln(Round(Mod360(5+720)));
outputs:
355
5
5
5
I don't know any, but I'd prefer using a more general solution anyway ...
Procedure IncOverFlow(var Value:Double;Difference:Double;Limit:Double=360);
begin
Value := Value + Difference;
While Value < 0 do Value := Value + Limit;
While Value >= Limit do Value := Value -Limit;
end;
procedure WrapAngle(var Degs: Integer);
begin
Degs := Degs mod 360;
if Degs < 0 then
Inc(Degs, 360);
end;