PageRenderTime 55ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/kolmath.pas

http://github.com/rofl0r/KOL
Pascal | 1631 lines | 1082 code | 186 blank | 363 comment | 102 complexity | dd033e8c48025758ec25a54e46fbd3fb MD5 | raw file
  1. {=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
  2. KKKKK KKKKK OOOOOOOOO LLLLL
  3. KKKKK KKKKK OOOOOOOOOOOOO LLLLL
  4. KKKKK KKKKK OOOOO OOOOO LLLLL
  5. KKKKK KKKKK OOOOO OOOOO LLLLL
  6. KKKKKKKKKK OOOOO OOOOO LLLLL
  7. KKKKK KKKKK OOOOO OOOOO LLLLL
  8. KKKKK KKKKK OOOOO OOOOO LLLLL
  9. KKKKK KKKKK OOOOOOOOOOOOO LLLLLLLLLLLLL
  10. KKKKK KKKKK OOOOOOOOO LLLLLLLLLLLLL
  11. Key Objects Library (C) 2000 by Kladov Vladimir.
  12. mailto: bonanzas@xcl.cjb.net
  13. Home: http://kol.nm.ru
  14. http://xcl.cjb.net
  15. http://xcl.nm.ru
  16. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-}
  17. {
  18. This code is grabbed from standard math.pas unit,
  19. provided by Borland Delphi. This unit is for working with
  20. engineering (mathematical) functions. The main difference
  21. is that err unit specially designed to handle exceptions
  22. for KOL is used instead of SysUtils. This allows to make
  23. size of the executable smaller for about 5K. though this
  24. value is insignificant for project made with VCL, it can
  25. be more than 15% of executable file size made with KOL.
  26. }
  27. {*******************************************************}
  28. { }
  29. { Borland Delphi Runtime Library }
  30. { Math Unit }
  31. { }
  32. { Copyright (C) 1996,99 Inprise Corporation }
  33. { }
  34. {*******************************************************}
  35. unit kolmath;
  36. { This unit contains high-performance arithmetic, trigonometric, logorithmic,
  37. statistical and financial calculation routines which supplement the math
  38. routines that are part of the Delphi language or System unit. }
  39. {$N+,S-}
  40. {$I KOLDEF.INC}
  41. interface
  42. uses err, kol;
  43. const { Ranges of the IEEE floating point types, including denormals }
  44. MinSingle = 1.5e-45;
  45. MaxSingle = 3.4e+38;
  46. MinDouble = 5.0e-324;
  47. MaxDouble = 1.7e+308;
  48. MinExtended = 3.4e-4932;
  49. MaxExtended = 1.1e+4932;
  50. MinComp = -9.223372036854775807e+18;
  51. MaxComp = 9.223372036854775807e+18;
  52. {-----------------------------------------------------------------------
  53. References:
  54. 1) P.J. Plauger, "The Standard C Library", Prentice-Hall, 1992, Ch. 7.
  55. 2) W.J. Cody, Jr., and W. Waite, "Software Manual For the Elementary
  56. Functions", Prentice-Hall, 1980.
  57. 3) Namir Shammas, "C/C++ Mathematical Algorithms for Scientists and Engineers",
  58. McGraw-Hill, 1995, Ch 8.
  59. 4) H.T. Lau, "A Numerical Library in C for Scientists and Engineers",
  60. CRC Press, 1994, Ch. 6.
  61. 5) "Pentium(tm) Processor User's Manual, Volume 3: Architecture
  62. and Programming Manual", Intel, 1994
  63. All angle parameters and results of trig functions are in radians.
  64. Most of the following trig and log routines map directly to Intel 80387 FPU
  65. floating point machine instructions. Input domains, output ranges, and
  66. error handling are determined largely by the FPU hardware.
  67. Routines coded in assembler favor the Pentium FPU pipeline architecture.
  68. -----------------------------------------------------------------------}
  69. function EAbs( D: Double ): Double;
  70. function EMax( const Values: array of Double ): Double;
  71. function EMin( const Values: array of Double ): Double;
  72. function iMax( const Values: array of Integer ): Integer;
  73. function iMin( const Values: array of Integer ): Integer;
  74. { Trigonometric functions }
  75. function ArcCos(X: Extended): Extended; { IN: |X| <= 1 OUT: [0..PI] radians }
  76. function ArcSin(X: Extended): Extended; { IN: |X| <= 1 OUT: [-PI/2..PI/2] radians }
  77. { ArcTan2 calculates ArcTan(Y/X), and returns an angle in the correct quadrant.
  78. IN: |Y| < 2^64, |X| < 2^64, X <> 0 OUT: [-PI..PI] radians }
  79. function ArcTan2(Y, X: Extended): Extended;
  80. { SinCos is 2x faster than calling Sin and Cos separately for the same angle }
  81. procedure SinCos(Theta: Extended; var Sin, Cos: Extended) register;
  82. function Tan(X: Extended): Extended;
  83. function Cotan(X: Extended): Extended; { 1 / tan(X), X <> 0 }
  84. function Hypot(X, Y: Extended): Extended; { Sqrt(X**2 + Y**2) }
  85. { Angle unit conversion routines }
  86. function DegToRad(Degrees: Extended): Extended; { Radians := Degrees * PI / 180}
  87. function RadToDeg(Radians: Extended): Extended; { Degrees := Radians * 180 / PI }
  88. function GradToRad(Grads: Extended): Extended; { Radians := Grads * PI / 200 }
  89. function RadToGrad(Radians: Extended): Extended; { Grads := Radians * 200 / PI }
  90. function CycleToRad(Cycles: Extended): Extended; { Radians := Cycles * 2PI }
  91. function RadToCycle(Radians: Extended): Extended;{ Cycles := Radians / 2PI }
  92. { Hyperbolic functions and inverses }
  93. function Cosh(X: Extended): Extended;
  94. function Sinh(X: Extended): Extended;
  95. function Tanh(X: Extended): Extended;
  96. function ArcCosh(X: Extended): Extended; { IN: X >= 1 }
  97. function ArcSinh(X: Extended): Extended;
  98. function ArcTanh(X: Extended): Extended; { IN: |X| <= 1 }
  99. { Logorithmic functions }
  100. function LnXP1(X: Extended): Extended; { Ln(X + 1), accurate for X near zero }
  101. function Log10(X: Extended): Extended; { Log base 10 of X}
  102. function Log2(X: Extended): Extended; { Log base 2 of X }
  103. function LogN(Base, X: Extended): Extended; { Log base N of X }
  104. { Exponential functions }
  105. { IntPower: Raise base to an integral power. Fast. }
  106. //function IntPower(Base: Extended; Exponent: Integer): Extended register;
  107. // -- already defined in kol.pas
  108. { Power: Raise base to any power.
  109. For fractional exponents, or |exponents| > MaxInt, base must be > 0. }
  110. function Power(Base, Exponent: Extended): Extended;
  111. { Miscellaneous Routines }
  112. { Frexp: Separates the mantissa and exponent of X. }
  113. procedure Frexp(X: Extended; var Mantissa: Extended; var Exponent: Integer) register;
  114. { Ldexp: returns X*2**P }
  115. function Ldexp(X: Extended; P: Integer): Extended register;
  116. { Ceil: Smallest integer >= X, |X| < MaxInt }
  117. function Ceil(X: Extended):Integer;
  118. { Floor: Largest integer <= X, |X| < MaxInt }
  119. function Floor(X: Extended): Integer;
  120. { Poly: Evaluates a uniform polynomial of one variable at value X.
  121. The coefficients are ordered in increasing powers of X:
  122. Coefficients[0] + Coefficients[1]*X + ... + Coefficients[N]*(X**N) }
  123. function Poly(X: Extended; const Coefficients: array of Double): Extended;
  124. {-----------------------------------------------------------------------
  125. Statistical functions.
  126. Common commercial spreadsheet macro names for these statistical and
  127. financial functions are given in the comments preceding each function.
  128. -----------------------------------------------------------------------}
  129. { Mean: Arithmetic average of values. (AVG): SUM / N }
  130. function Mean(const Data: array of Double): Extended;
  131. { Sum: Sum of values. (SUM) }
  132. function Sum(const Data: array of Double): Extended register;
  133. function SumInt(const Data: array of Integer): Integer register;
  134. function SumOfSquares(const Data: array of Double): Extended;
  135. procedure SumsAndSquares(const Data: array of Double;
  136. var Sum, SumOfSquares: Extended) register;
  137. { MinValue: Returns the smallest signed value in the data array (MIN) }
  138. function MinValue(const Data: array of Double): Double;
  139. function MinIntValue(const Data: array of Integer): Integer;
  140. function Min(A,B: Integer): Integer;
  141. {$IFDEF _D4orHigher}
  142. overload;
  143. function Min(A,B: I64): I64; overload;
  144. function Min(A,B: Single): Single; overload;
  145. function Min(A,B: Double): Double; overload;
  146. function Min(A,B: Extended): Extended; overload;
  147. {$ENDIF}
  148. { MaxValue: Returns the largest signed value in the data array (MAX) }
  149. function MaxValue(const Data: array of Double): Double;
  150. function MaxIntValue(const Data: array of Integer): Integer;
  151. function Max(A,B: Integer): Integer;
  152. {$IFDEF _D4orHigher}
  153. overload;
  154. function Max(A,B: I64): I64; overload;
  155. function Max(A,B: Single): Single; overload;
  156. function Max(A,B: Double): Double; overload;
  157. function Max(A,B: Extended): Extended; overload;
  158. {$ENDIF}
  159. { Standard Deviation (STD): Sqrt(Variance). aka Sample Standard Deviation }
  160. function StdDev(const Data: array of Double): Extended;
  161. { MeanAndStdDev calculates Mean and StdDev in one call. }
  162. procedure MeanAndStdDev(const Data: array of Double; var Mean, StdDev: Extended);
  163. { Population Standard Deviation (STDP): Sqrt(PopnVariance).
  164. Used in some business and financial calculations. }
  165. function PopnStdDev(const Data: array of Double): Extended;
  166. { Variance (VARS): TotalVariance / (N-1). aka Sample Variance }
  167. function Variance(const Data: array of Double): Extended;
  168. { Population Variance (VAR or VARP): TotalVariance/ N }
  169. function PopnVariance(const Data: array of Double): Extended;
  170. { Total Variance: SUM(i=1,N)[(X(i) - Mean)**2] }
  171. function TotalVariance(const Data: array of Double): Extended;
  172. { Norm: The Euclidean L2-norm. Sqrt(SumOfSquares) }
  173. function Norm(const Data: array of Double): Extended;
  174. { MomentSkewKurtosis: Calculates the core factors of statistical analysis:
  175. the first four moments plus the coefficients of skewness and kurtosis.
  176. M1 is the Mean. M2 is the Variance.
  177. Skew reflects symmetry of distribution: M3 / (M2**(3/2))
  178. Kurtosis reflects flatness of distribution: M4 / Sqr(M2) }
  179. procedure MomentSkewKurtosis(const Data: array of Double;
  180. var M1, M2, M3, M4, Skew, Kurtosis: Extended);
  181. { RandG produces random numbers with Gaussian distribution about the mean.
  182. Useful for simulating data with sampling errors. }
  183. function RandG(Mean, StdDev: Extended): Extended;
  184. {-----------------------------------------------------------------------
  185. Financial functions. Standard set from Quattro Pro.
  186. Parameter conventions:
  187. From the point of view of A, amounts received by A are positive and
  188. amounts disbursed by A are negative (e.g. a borrower's loan repayments
  189. are regarded by the borrower as negative).
  190. Interest rates are per payment period. 11% annual percentage rate on a
  191. loan with 12 payments per year would be (11 / 100) / 12 = 0.00916667
  192. -----------------------------------------------------------------------}
  193. type
  194. TPaymentTime = (ptEndOfPeriod, ptStartOfPeriod);
  195. { Double Declining Balance (DDB) }
  196. function DoubleDecliningBalance(Cost, Salvage: Extended;
  197. Life, Period: Integer): Extended;
  198. { Future Value (FVAL) }
  199. function FutureValue(Rate: Extended; NPeriods: Integer; Payment, PresentValue:
  200. Extended; PaymentTime: TPaymentTime): Extended;
  201. { Interest Payment (IPAYMT) }
  202. function InterestPayment(Rate: Extended; Period, NPeriods: Integer; PresentValue,
  203. FutureValue: Extended; PaymentTime: TPaymentTime): Extended;
  204. { Interest Rate (IRATE) }
  205. function InterestRate(NPeriods: Integer;
  206. Payment, PresentValue, FutureValue: Extended; PaymentTime: TPaymentTime): Extended;
  207. { Internal Rate of Return. (IRR) Needs array of cash flows. }
  208. function InternalRateOfReturn(Guess: Extended;
  209. const CashFlows: array of Double): Extended;
  210. { Number of Periods (NPER) }
  211. function NumberOfPeriods(Rate, Payment, PresentValue, FutureValue: Extended;
  212. PaymentTime: TPaymentTime): Extended;
  213. { Net Present Value. (NPV) Needs array of cash flows. }
  214. function NetPresentValue(Rate: Extended; const CashFlows: array of Double;
  215. PaymentTime: TPaymentTime): Extended;
  216. { Payment (PAYMT) }
  217. function Payment(Rate: Extended; NPeriods: Integer;
  218. PresentValue, FutureValue: Extended; PaymentTime: TPaymentTime): Extended;
  219. { Period Payment (PPAYMT) }
  220. function PeriodPayment(Rate: Extended; Period, NPeriods: Integer;
  221. PresentValue, FutureValue: Extended; PaymentTime: TPaymentTime): Extended;
  222. { Present Value (PVAL) }
  223. function PresentValue(Rate: Extended; NPeriods: Integer;
  224. Payment, FutureValue: Extended; PaymentTime: TPaymentTime): Extended;
  225. { Straight Line depreciation (SLN) }
  226. function SLNDepreciation(Cost, Salvage: Extended; Life: Integer): Extended;
  227. { Sum-of-Years-Digits depreciation (SYD) }
  228. function SYDDepreciation(Cost, Salvage: Extended; Life, Period: Integer): Extended;
  229. {type
  230. EInvalidArgument = class(EMathError) end;}
  231. implementation
  232. {$IFNDEF _D2orD3}
  233. uses SysConst;
  234. {$ENDIF}
  235. function EAbs( D: Double ): Double;
  236. begin
  237. Result := D;
  238. if Result < 0.0 then
  239. Result := -Result;
  240. end;
  241. function EMax( const Values: array of Double ): Double;
  242. var I: Integer;
  243. begin
  244. Result := Values[ 0 ];
  245. for I := 1 to High( Values ) do
  246. if Result < Values[ I ] then Result := Values[ I ];
  247. end;
  248. function EMin( const Values: array of Double ): Double;
  249. var I: Integer;
  250. begin
  251. Result := Values[ 0 ];
  252. for I := 1 to High( Values ) do
  253. if Result > Values[ I ] then Result := Values[ I ];
  254. end;
  255. function iMax( const Values: array of Integer ): Integer;
  256. var I: Integer;
  257. begin
  258. Result := Values[ 0 ];
  259. for I := 1 to High( Values ) do
  260. if Result < Values[ I ] then Result := Values[ I ];
  261. end;
  262. function iMin( const Values: array of Integer ): Integer;
  263. var I: Integer;
  264. begin
  265. Result := Values[ 0 ];
  266. for I := 1 to High( Values ) do
  267. if Result > Values[ I ] then Result := Values[ I ];
  268. end;
  269. function Annuity2(R: Extended; N: Integer; PaymentTime: TPaymentTime;
  270. var CompoundRN: Extended): Extended; Forward;
  271. function Compound(R: Extended; N: Integer): Extended; Forward;
  272. function RelSmall(X, Y: Extended): Boolean; Forward;
  273. type
  274. TPoly = record
  275. Neg, Pos, DNeg, DPos: Extended
  276. end;
  277. const
  278. MaxIterations = 15;
  279. procedure ArgError(const Msg: string);
  280. begin
  281. raise Exception.Create(e_Math_InvalidArgument, Msg);
  282. end;
  283. function DegToRad(Degrees: Extended): Extended; { Radians := Degrees * PI / 180 }
  284. begin
  285. Result := Degrees * (PI / 180);
  286. end;
  287. function RadToDeg(Radians: Extended): Extended; { Degrees := Radians * 180 / PI }
  288. begin
  289. Result := Radians * (180 / PI);
  290. end;
  291. function GradToRad(Grads: Extended): Extended; { Radians := Grads * PI / 200 }
  292. begin
  293. Result := Grads * (PI / 200);
  294. end;
  295. function RadToGrad(Radians: Extended): Extended; { Grads := Radians * 200 / PI}
  296. begin
  297. Result := Radians * (200 / PI);
  298. end;
  299. function CycleToRad(Cycles: Extended): Extended; { Radians := Cycles * 2PI }
  300. begin
  301. Result := Cycles * (2 * PI);
  302. end;
  303. function RadToCycle(Radians: Extended): Extended;{ Cycles := Radians / 2PI }
  304. begin
  305. Result := Radians / (2 * PI);
  306. end;
  307. function LnXP1(X: Extended): Extended;
  308. { Return ln(1 + X). Accurate for X near 0. }
  309. asm
  310. FLDLN2
  311. MOV AX,WORD PTR X+8 { exponent }
  312. FLD X
  313. CMP AX,$3FFD { .4225 }
  314. JB @@1
  315. FLD1
  316. FADD
  317. FYL2X
  318. JMP @@2
  319. @@1:
  320. FYL2XP1
  321. @@2:
  322. FWAIT
  323. end;
  324. { Invariant: Y >= 0 & Result*X**Y = X**I. Init Y = I and Result = 1. }
  325. {function IntPower(X: Extended; I: Integer): Extended;
  326. var
  327. Y: Integer;
  328. begin
  329. Y := Abs(I);
  330. Result := 1.0;
  331. while Y > 0 do begin
  332. while not Odd(Y) do
  333. begin
  334. Y := Y shr 1;
  335. X := X * X
  336. end;
  337. Dec(Y);
  338. Result := Result * X
  339. end;
  340. if I < 0 then Result := 1.0 / Result
  341. end;
  342. }
  343. (* -- already defined in kol.pas
  344. function IntPower(Base: Extended; Exponent: Integer): Extended;
  345. asm
  346. mov ecx, eax
  347. cdq
  348. fld1 { Result := 1 }
  349. xor eax, edx
  350. sub eax, edx { eax := Abs(Exponent) }
  351. jz @@3
  352. fld Base
  353. jmp @@2
  354. @@1: fmul ST, ST { X := Base * Base }
  355. @@2: shr eax,1
  356. jnc @@1
  357. fmul ST(1),ST { Result := Result * X }
  358. jnz @@1
  359. fstp st { pop X from FPU stack }
  360. cmp ecx, 0
  361. jge @@3
  362. fld1
  363. fdivrp { Result := 1 / Result }
  364. @@3:
  365. fwait
  366. end;
  367. *)
  368. function Compound(R: Extended; N: Integer): Extended;
  369. { Return (1 + R)**N. }
  370. begin
  371. Result := IntPower(1.0 + R, N)
  372. end;
  373. function Annuity2(R: Extended; N: Integer; PaymentTime: TPaymentTime;
  374. var CompoundRN: Extended): Extended;
  375. { Set CompoundRN to Compound(R, N),
  376. return (1+Rate*PaymentTime)*(Compound(R,N)-1)/R;
  377. }
  378. begin
  379. if R = 0.0 then
  380. begin
  381. CompoundRN := 1.0;
  382. Result := N;
  383. end
  384. else
  385. begin
  386. { 6.1E-5 approx= 2**-14 }
  387. if EAbs(R) < 6.1E-5 then
  388. begin
  389. CompoundRN := Exp(N * LnXP1(R));
  390. Result := N*(1+(N-1)*R/2);
  391. end
  392. else
  393. begin
  394. CompoundRN := Compound(R, N);
  395. Result := (CompoundRN-1) / R
  396. end;
  397. if PaymentTime = ptStartOfPeriod then
  398. Result := Result * (1 + R);
  399. end;
  400. end; {Annuity2}
  401. procedure PolyX(const A: array of Double; X: Extended; var Poly: TPoly);
  402. { Compute A[0] + A[1]*X + ... + A[N]*X**N and X * its derivative.
  403. Accumulate positive and negative terms separately. }
  404. var
  405. I: Integer;
  406. Neg, Pos, DNeg, DPos: Extended;
  407. begin
  408. Neg := 0.0;
  409. Pos := 0.0;
  410. DNeg := 0.0;
  411. DPos := 0.0;
  412. for I := High(A) downto Low(A) do
  413. begin
  414. DNeg := X * DNeg + Neg;
  415. Neg := Neg * X;
  416. DPos := X * DPos + Pos;
  417. Pos := Pos * X;
  418. if A[I] >= 0.0 then
  419. Pos := Pos + A[I]
  420. else
  421. Neg := Neg + A[I]
  422. end;
  423. Poly.Neg := Neg;
  424. Poly.Pos := Pos;
  425. Poly.DNeg := DNeg * X;
  426. Poly.DPos := DPos * X;
  427. end; {PolyX}
  428. function RelSmall(X, Y: Extended): Boolean;
  429. { Returns True if X is small relative to Y }
  430. const
  431. C1: Double = 1E-15;
  432. C2: Double = 1E-12;
  433. begin
  434. Result := EAbs(X) < (C1 + C2 * EAbs(Y))
  435. end;
  436. { Math functions. }
  437. function ArcCos(X: Extended): Extended;
  438. begin
  439. Result := ArcTan2(Sqrt(1 - X*X), X);
  440. end;
  441. function ArcSin(X: Extended): Extended;
  442. begin
  443. Result := ArcTan2(X, Sqrt(1 - X*X))
  444. end;
  445. function ArcTan2(Y, X: Extended): Extended;
  446. asm
  447. FLD Y
  448. FLD X
  449. FPATAN
  450. FWAIT
  451. end;
  452. function Tan(X: Extended): Extended;
  453. { Tan := Sin(X) / Cos(X) }
  454. asm
  455. FLD X
  456. FPTAN
  457. FSTP ST(0) { FPTAN pushes 1.0 after result }
  458. FWAIT
  459. end;
  460. function CoTan(X: Extended): Extended;
  461. { CoTan := Cos(X) / Sin(X) = 1 / Tan(X) }
  462. asm
  463. FLD X
  464. FPTAN
  465. FDIVRP
  466. FWAIT
  467. end;
  468. function Hypot(X, Y: Extended): Extended;
  469. { formula: Sqrt(X*X + Y*Y)
  470. implemented as: |Y|*Sqrt(1+Sqr(X/Y)), |X| < |Y| for greater precision
  471. var
  472. Temp: Extended;
  473. begin
  474. X := Abs(X);
  475. Y := Abs(Y);
  476. if X > Y then
  477. begin
  478. Temp := X;
  479. X := Y;
  480. Y := Temp;
  481. end;
  482. if X = 0 then
  483. Result := Y
  484. else // Y > X, X <> 0, so Y > 0
  485. Result := Y * Sqrt(1 + Sqr(X/Y));
  486. end;
  487. }
  488. asm
  489. FLD Y
  490. FABS
  491. FLD X
  492. FABS
  493. FCOM
  494. FNSTSW AX
  495. TEST AH,$45
  496. JNZ @@1 // if ST > ST(1) then swap
  497. FXCH ST(1) // put larger number in ST(1)
  498. @@1: FLDZ
  499. FCOMP
  500. FNSTSW AX
  501. TEST AH,$40 // if ST = 0, return ST(1)
  502. JZ @@2
  503. FSTP ST // eat ST(0)
  504. JMP @@3
  505. @@2: FDIV ST,ST(1) // ST := ST / ST(1)
  506. FMUL ST,ST // ST := ST * ST
  507. FLD1
  508. FADD // ST := ST + 1
  509. FSQRT // ST := Sqrt(ST)
  510. FMUL // ST(1) := ST * ST(1); Pop ST
  511. @@3: FWAIT
  512. end;
  513. procedure SinCos(Theta: Extended; var Sin, Cos: Extended);
  514. asm
  515. FLD Theta
  516. FSINCOS
  517. FSTP tbyte ptr [edx] // Cos
  518. FSTP tbyte ptr [eax] // Sin
  519. FWAIT
  520. end;
  521. { Extract exponent and mantissa from X }
  522. procedure Frexp(X: Extended; var Mantissa: Extended; var Exponent: Integer);
  523. { Mantissa ptr in EAX, Exponent ptr in EDX }
  524. asm
  525. FLD X
  526. PUSH EAX
  527. MOV dword ptr [edx], 0 { if X = 0, return 0 }
  528. FTST
  529. FSTSW AX
  530. FWAIT
  531. SAHF
  532. JZ @@Done
  533. FXTRACT // ST(1) = exponent, (pushed) ST = fraction
  534. FXCH
  535. // The FXTRACT instruction normalizes the fraction 1 bit higher than
  536. // wanted for the definition of frexp() so we need to tweak the result
  537. // by scaling the fraction down and incrementing the exponent.
  538. FISTP dword ptr [edx]
  539. FLD1
  540. FCHS
  541. FXCH
  542. FSCALE // scale fraction
  543. INC dword ptr [edx] // exponent biased to match
  544. FSTP ST(1) // discard -1, leave fraction as TOS
  545. @@Done:
  546. POP EAX
  547. FSTP tbyte ptr [eax]
  548. FWAIT
  549. end;
  550. function Ldexp(X: Extended; P: Integer): Extended;
  551. { Result := X * (2^P) }
  552. asm
  553. PUSH EAX
  554. FILD dword ptr [ESP]
  555. FLD X
  556. FSCALE
  557. POP EAX
  558. FSTP ST(1)
  559. FWAIT
  560. end;
  561. function Ceil(X: Extended): Integer;
  562. begin
  563. Result := Integer(Trunc(X));
  564. if Frac(X) > 0 then
  565. Inc(Result);
  566. end;
  567. function Floor(X: Extended): Integer;
  568. begin
  569. Result := Integer(Trunc(X));
  570. if Frac(X) < 0 then
  571. Dec(Result);
  572. end;
  573. { Conversion of bases: Log.b(X) = Log.a(X) / Log.a(b) }
  574. function Log10(X: Extended): Extended;
  575. { Log.10(X) := Log.2(X) * Log.10(2) }
  576. asm
  577. FLDLG2 { Log base ten of 2 }
  578. FLD X
  579. FYL2X
  580. FWAIT
  581. end;
  582. function Log2(X: Extended): Extended;
  583. asm
  584. FLD1
  585. FLD X
  586. FYL2X
  587. FWAIT
  588. end;
  589. function LogN(Base, X: Extended): Extended;
  590. { Log.N(X) := Log.2(X) / Log.2(N) }
  591. asm
  592. FLD1
  593. FLD X
  594. FYL2X
  595. FLD1
  596. FLD Base
  597. FYL2X
  598. FDIV
  599. FWAIT
  600. end;
  601. function Poly(X: Extended; const Coefficients: array of Double): Extended;
  602. { Horner's method }
  603. var
  604. I: Integer;
  605. begin
  606. Result := Coefficients[High(Coefficients)];
  607. for I := High(Coefficients)-1 downto Low(Coefficients) do
  608. Result := Result * X + Coefficients[I];
  609. end;
  610. function Power(Base, Exponent: Extended): Extended;
  611. begin
  612. if Exponent = 0.0 then
  613. Result := 1.0 { n**0 = 1 }
  614. else if (Base = 0.0) and (Exponent > 0.0) then
  615. Result := 0.0 { 0**n = 0, n > 0 }
  616. else if (Frac(Exponent) = 0.0) and (EAbs(Exponent) <= MaxInt) then
  617. Result := IntPower(Base, Integer(Trunc(Exponent)))
  618. else
  619. Result := Exp(Exponent * Ln(Base))
  620. end;
  621. { Hyperbolic functions }
  622. function CoshSinh(X: Extended; Factor: Double): Extended;
  623. begin
  624. Result := Exp(X) / 2;
  625. Result := Result + Factor / Result;
  626. end;
  627. function Cosh(X: Extended): Extended;
  628. begin
  629. Result := CoshSinh(X, 0.25)
  630. end;
  631. function Sinh(X: Extended): Extended;
  632. begin
  633. Result := CoshSinh(X, -0.25)
  634. end;
  635. const
  636. MaxTanhDomain = 5678.22249441322; // Ln(MaxExtended)/2
  637. function Tanh(X: Extended): Extended;
  638. begin
  639. if X > MaxTanhDomain then
  640. Result := 1.0
  641. else if X < -MaxTanhDomain then
  642. Result := -1.0
  643. else
  644. begin
  645. Result := Exp(X);
  646. Result := Result * Result;
  647. Result := (Result - 1.0) / (Result + 1.0)
  648. end;
  649. end;
  650. function ArcCosh(X: Extended): Extended;
  651. begin
  652. if X <= 1.0 then
  653. Result := 0.0
  654. else if X > 1.0e10 then
  655. Result := Ln(2) + Ln(X)
  656. else
  657. Result := Ln(X + Sqrt((X - 1.0) * (X + 1.0)));
  658. end;
  659. function ArcSinh(X: Extended): Extended;
  660. var
  661. Neg: Boolean;
  662. begin
  663. if X = 0 then
  664. Result := 0
  665. else
  666. begin
  667. Neg := (X < 0);
  668. X := EAbs(X);
  669. if X > 1.0e10 then
  670. Result := Ln(2) + Ln(X)
  671. else
  672. begin
  673. Result := X*X;
  674. Result := LnXP1(X + Result / (1 + Sqrt(1 + Result)));
  675. end;
  676. if Neg then Result := -Result;
  677. end;
  678. end;
  679. function ArcTanh(X: Extended): Extended;
  680. var
  681. Neg: Boolean;
  682. begin
  683. if X = 0 then
  684. Result := 0
  685. else
  686. begin
  687. Neg := (X < 0);
  688. X := EAbs(X);
  689. if X >= 1 then
  690. Result := MaxExtended
  691. else
  692. Result := 0.5 * LnXP1((2.0 * X) / (1.0 - X));
  693. if Neg then Result := -Result;
  694. end;
  695. end;
  696. { Statistical functions }
  697. function Mean(const Data: array of Double): Extended;
  698. begin
  699. Result := SUM(Data) / (High(Data) - Low(Data) + 1)
  700. end;
  701. function MinValue(const Data: array of Double): Double;
  702. var
  703. I: Integer;
  704. begin
  705. Result := Data[Low(Data)];
  706. for I := Low(Data) + 1 to High(Data) do
  707. if Result > Data[I] then
  708. Result := Data[I];
  709. end;
  710. function MinIntValue(const Data: array of Integer): Integer;
  711. var
  712. I: Integer;
  713. begin
  714. Result := Data[Low(Data)];
  715. for I := Low(Data) + 1 to High(Data) do
  716. if Result > Data[I] then
  717. Result := Data[I];
  718. end;
  719. function Min(A,B: Integer): Integer;
  720. begin
  721. if A < B then
  722. Result := A
  723. else
  724. Result := B;
  725. end;
  726. {$IFDEF _D4orHigher}
  727. function Min(A,B: I64): I64;
  728. begin
  729. if Cmp64( A, B ) < 0 then
  730. Result := A
  731. else
  732. Result := B;
  733. end;
  734. function Min(A,B: Single): Single;
  735. begin
  736. if A < B then
  737. Result := A
  738. else
  739. Result := B;
  740. end;
  741. function Min(A,B: Double): Double;
  742. begin
  743. if A < B then
  744. Result := A
  745. else
  746. Result := B;
  747. end;
  748. function Min(A,B: Extended): Extended;
  749. begin
  750. if A < B then
  751. Result := A
  752. else
  753. Result := B;
  754. end;
  755. {$ENDIF}
  756. function MaxValue(const Data: array of Double): Double;
  757. var
  758. I: Integer;
  759. begin
  760. Result := Data[Low(Data)];
  761. for I := Low(Data) + 1 to High(Data) do
  762. if Result < Data[I] then
  763. Result := Data[I];
  764. end;
  765. function MaxIntValue(const Data: array of Integer): Integer;
  766. var
  767. I: Integer;
  768. begin
  769. Result := Data[Low(Data)];
  770. for I := Low(Data) + 1 to High(Data) do
  771. if Result < Data[I] then
  772. Result := Data[I];
  773. end;
  774. function Max(A,B: Integer): Integer;
  775. begin
  776. if A > B then
  777. Result := A
  778. else
  779. Result := B;
  780. end;
  781. {$IFDEF _D4orHigher}
  782. function Max(A,B: I64): I64;
  783. begin
  784. if Cmp64( A, B ) > 0 then
  785. Result := A
  786. else
  787. Result := B;
  788. end;
  789. function Max(A,B: Single): Single;
  790. begin
  791. if A > B then
  792. Result := A
  793. else
  794. Result := B;
  795. end;
  796. function Max(A,B: Double): Double;
  797. begin
  798. if A > B then
  799. Result := A
  800. else
  801. Result := B;
  802. end;
  803. function Max(A,B: Extended): Extended;
  804. begin
  805. if A > B then
  806. Result := A
  807. else
  808. Result := B;
  809. end;
  810. {$ENDIF}
  811. procedure MeanAndStdDev(const Data: array of Double; var Mean, StdDev: Extended);
  812. var
  813. S: Extended;
  814. N,I: Integer;
  815. begin
  816. N := High(Data)- Low(Data) + 1;
  817. if N = 1 then
  818. begin
  819. Mean := Data[0];
  820. StdDev := Data[0];
  821. Exit;
  822. end;
  823. Mean := Sum(Data) / N;
  824. S := 0; // sum differences from the mean, for greater accuracy
  825. for I := Low(Data) to High(Data) do
  826. S := S + Sqr(Mean - Data[I]);
  827. StdDev := Sqrt(S / (N - 1));
  828. end;
  829. procedure MomentSkewKurtosis(const Data: array of Double;
  830. var M1, M2, M3, M4, Skew, Kurtosis: Extended);
  831. var
  832. Sum, SumSquares, SumCubes, SumQuads, OverN, Accum, M1Sqr, S2N, S3N: Extended;
  833. I: Integer;
  834. begin
  835. OverN := 1 / (High(Data) - Low(Data) + 1);
  836. Sum := 0;
  837. SumSquares := 0;
  838. SumCubes := 0;
  839. SumQuads := 0;
  840. for I := Low(Data) to High(Data) do
  841. begin
  842. Sum := Sum + Data[I];
  843. Accum := Sqr(Data[I]);
  844. SumSquares := SumSquares + Accum;
  845. Accum := Accum*Data[I];
  846. SumCubes := SumCubes + Accum;
  847. SumQuads := SumQuads + Accum*Data[I];
  848. end;
  849. M1 := Sum * OverN;
  850. M1Sqr := Sqr(M1);
  851. S2N := SumSquares * OverN;
  852. S3N := SumCubes * OverN;
  853. M2 := S2N - M1Sqr;
  854. M3 := S3N - (M1 * 3 * S2N) + 2*M1Sqr*M1;
  855. M4 := (SumQuads * OverN) - (M1 * 4 * S3N) + (M1Sqr*6*S2N - 3*Sqr(M1Sqr));
  856. Skew := M3 * Power(M2, -3/2); // = M3 / Power(M2, 3/2)
  857. Kurtosis := M4 / Sqr(M2);
  858. end;
  859. function Norm(const Data: array of Double): Extended;
  860. begin
  861. Result := Sqrt(SumOfSquares(Data));
  862. end;
  863. function PopnStdDev(const Data: array of Double): Extended;
  864. begin
  865. Result := Sqrt(PopnVariance(Data))
  866. end;
  867. function PopnVariance(const Data: array of Double): Extended;
  868. begin
  869. Result := TotalVariance(Data) / (High(Data) - Low(Data) + 1)
  870. end;
  871. function RandG(Mean, StdDev: Extended): Extended;
  872. { Marsaglia-Bray algorithm }
  873. var
  874. U1, S2: Extended;
  875. begin
  876. repeat
  877. U1 := 2*Random - 1;
  878. S2 := Sqr(U1) + Sqr(2*Random-1);
  879. until S2 < 1;
  880. Result := Sqrt(-2*Ln(S2)/S2) * U1 * StdDev + Mean;
  881. end;
  882. function StdDev(const Data: array of Double): Extended;
  883. begin
  884. Result := Sqrt(Variance(Data))
  885. end;
  886. procedure RaiseOverflowError; forward;
  887. function SumInt(const Data: array of Integer): Integer;
  888. {var
  889. I: Integer;
  890. begin
  891. Result := 0;
  892. for I := Low(Data) to High(Data) do
  893. Result := Result + Data[I]
  894. end; }
  895. asm // IN: EAX = ptr to Data, EDX = High(Data) = Count - 1
  896. // loop unrolled 4 times, 5 clocks per loop, 1.2 clocks per datum
  897. PUSH EBX
  898. MOV ECX, EAX // ecx = ptr to data
  899. MOV EBX, EDX
  900. XOR EAX, EAX
  901. AND EDX, not 3
  902. AND EBX, 3
  903. SHL EDX, 2
  904. JMP @Vector.Pointer[EBX*4]
  905. @Vector:
  906. DD @@1
  907. DD @@2
  908. DD @@3
  909. DD @@4
  910. @@4:
  911. ADD EAX, [ECX+12+EDX]
  912. JO RaiseOverflowError
  913. @@3:
  914. ADD EAX, [ECX+8+EDX]
  915. JO RaiseOverflowError
  916. @@2:
  917. ADD EAX, [ECX+4+EDX]
  918. JO RaiseOverflowError
  919. @@1:
  920. ADD EAX, [ECX+EDX]
  921. JO RaiseOverflowError
  922. SUB EDX,16
  923. JNS @@4
  924. POP EBX
  925. end;
  926. procedure RaiseOverflowError;
  927. begin
  928. raise Exception.Create(e_IntOverflow, SIntOverflow);
  929. end;
  930. function SUM(const Data: array of Double): Extended;
  931. {var
  932. I: Integer;
  933. begin
  934. Result := 0.0;
  935. for I := Low(Data) to High(Data) do
  936. Result := Result + Data[I]
  937. end; }
  938. asm // IN: EAX = ptr to Data, EDX = High(Data) = Count - 1
  939. // Uses 4 accumulators to minimize read-after-write delays and loop overhead
  940. // 5 clocks per loop, 4 items per loop = 1.2 clocks per item
  941. FLDZ
  942. MOV ECX, EDX
  943. FLD ST(0)
  944. AND EDX, not 3
  945. FLD ST(0)
  946. AND ECX, 3
  947. FLD ST(0)
  948. SHL EDX, 3 // count * sizeof(Double) = count * 8
  949. JMP @Vector.Pointer[ECX*4]
  950. @Vector:
  951. DD @@1
  952. DD @@2
  953. DD @@3
  954. DD @@4
  955. @@4: FADD qword ptr [EAX+EDX+24] // 1
  956. FXCH ST(3) // 0
  957. @@3: FADD qword ptr [EAX+EDX+16] // 1
  958. FXCH ST(2) // 0
  959. @@2: FADD qword ptr [EAX+EDX+8] // 1
  960. FXCH ST(1) // 0
  961. @@1: FADD qword ptr [EAX+EDX] // 1
  962. FXCH ST(2) // 0
  963. SUB EDX, 32
  964. JNS @@4
  965. FADDP ST(3),ST // ST(3) := ST + ST(3); Pop ST
  966. FADD // ST(1) := ST + ST(1); Pop ST
  967. FADD // ST(1) := ST + ST(1); Pop ST
  968. FWAIT
  969. end;
  970. function SumOfSquares(const Data: array of Double): Extended;
  971. var
  972. I: Integer;
  973. begin
  974. Result := 0.0;
  975. for I := Low(Data) to High(Data) do
  976. Result := Result + Sqr(Data[I]);
  977. end;
  978. procedure SumsAndSquares(const Data: array of Double; var Sum, SumOfSquares: Extended);
  979. {var
  980. I: Integer;
  981. begin
  982. Sum := 0;
  983. SumOfSquares := 0;
  984. for I := Low(Data) to High(Data) do
  985. begin
  986. Sum := Sum + Data[I];
  987. SumOfSquares := SumOfSquares + Data[I]*Data[I];
  988. end;
  989. end; }
  990. asm // IN: EAX = ptr to Data
  991. // EDX = High(Data) = Count - 1
  992. // ECX = ptr to Sum
  993. // Est. 17 clocks per loop, 4 items per loop = 4.5 clocks per data item
  994. FLDZ // init Sum accumulator
  995. PUSH ECX
  996. MOV ECX, EDX
  997. FLD ST(0) // init Sqr1 accum.
  998. AND EDX, not 3
  999. FLD ST(0) // init Sqr2 accum.
  1000. AND ECX, 3
  1001. FLD ST(0) // init/simulate last data item left in ST
  1002. SHL EDX, 3 // count * sizeof(Double) = count * 8
  1003. JMP @Vector.Pointer[ECX*4]
  1004. @Vector:
  1005. DD @@1
  1006. DD @@2
  1007. DD @@3
  1008. DD @@4
  1009. @@4: FADD // Sqr2 := Sqr2 + Sqr(Data4); Pop Data4
  1010. FLD qword ptr [EAX+EDX+24] // Load Data1
  1011. FADD ST(3),ST // Sum := Sum + Data1
  1012. FMUL ST,ST // Data1 := Sqr(Data1)
  1013. @@3: FLD qword ptr [EAX+EDX+16] // Load Data2
  1014. FADD ST(4),ST // Sum := Sum + Data2
  1015. FMUL ST,ST // Data2 := Sqr(Data2)
  1016. FXCH // Move Sqr(Data1) into ST(0)
  1017. FADDP ST(3),ST // Sqr1 := Sqr1 + Sqr(Data1); Pop Data1
  1018. @@2: FLD qword ptr [EAX+EDX+8] // Load Data3
  1019. FADD ST(4),ST // Sum := Sum + Data3
  1020. FMUL ST,ST // Data3 := Sqr(Data3)
  1021. FXCH // Move Sqr(Data2) into ST(0)
  1022. FADDP ST(3),ST // Sqr1 := Sqr1 + Sqr(Data2); Pop Data2
  1023. @@1: FLD qword ptr [EAX+EDX] // Load Data4
  1024. FADD ST(4),ST // Sum := Sum + Data4
  1025. FMUL ST,ST // Sqr(Data4)
  1026. FXCH // Move Sqr(Data3) into ST(0)
  1027. FADDP ST(3),ST // Sqr1 := Sqr1 + Sqr(Data3); Pop Data3
  1028. SUB EDX,32
  1029. JNS @@4
  1030. FADD // Sqr2 := Sqr2 + Sqr(Data4); Pop Data4
  1031. POP ECX
  1032. FADD // Sqr1 := Sqr2 + Sqr1; Pop Sqr2
  1033. FXCH // Move Sum1 into ST(0)
  1034. MOV EAX, SumOfSquares
  1035. FSTP tbyte ptr [ECX] // Sum := Sum1; Pop Sum1
  1036. FSTP tbyte ptr [EAX] // SumOfSquares := Sum1; Pop Sum1
  1037. FWAIT
  1038. end;
  1039. function TotalVariance(const Data: array of Double): Extended;
  1040. var
  1041. Sum, SumSquares: Extended;
  1042. begin
  1043. SumsAndSquares(Data, Sum, SumSquares);
  1044. Result := SumSquares - Sqr(Sum)/(High(Data) - Low(Data) + 1);
  1045. end;
  1046. function Variance(const Data: array of Double): Extended;
  1047. begin
  1048. Result := TotalVariance(Data) / (High(Data) - Low(Data))
  1049. end;
  1050. { Depreciation functions. }
  1051. function DoubleDecliningBalance(Cost, Salvage: Extended; Life, Period: Integer): Extended;
  1052. { dv := cost * (1 - 2/life)**(period - 1)
  1053. DDB = (2/life) * dv
  1054. if DDB > dv - salvage then DDB := dv - salvage
  1055. if DDB < 0 then DDB := 0
  1056. }
  1057. var
  1058. DepreciatedVal, Factor: Extended;
  1059. begin
  1060. Result := 0;
  1061. if (Period < 1) or (Life < Period) or (Life < 1) or (Cost <= Salvage) then
  1062. Exit;
  1063. {depreciate everything in period 1 if life is only one or two periods}
  1064. if ( Life <= 2 ) then
  1065. begin
  1066. if ( Period = 1 ) then
  1067. DoubleDecliningBalance:=Cost-Salvage
  1068. else
  1069. DoubleDecliningBalance:=0; {all depreciation occurred in first period}
  1070. exit;
  1071. end;
  1072. Factor := 2.0 / Life;
  1073. DepreciatedVal := Cost * IntPower((1.0 - Factor), Period - 1);
  1074. {DepreciatedVal is Cost-(sum of previous depreciation results)}
  1075. Result := Factor * DepreciatedVal;
  1076. {Nominal computed depreciation for this period. The rest of the
  1077. function applies limits to this nominal value. }
  1078. {Only depreciate until total depreciation equals cost-salvage.}
  1079. if Result > DepreciatedVal - Salvage then
  1080. Result := DepreciatedVal - Salvage;
  1081. {No more depreciation after salvage value is reached. This is mostly a nit.
  1082. If Result is negative at this point, it's very close to zero.}
  1083. if Result < 0.0 then
  1084. Result := 0.0;
  1085. end;
  1086. function SLNDepreciation(Cost, Salvage: Extended; Life: Integer): Extended;
  1087. { Spreads depreciation linearly over life. }
  1088. begin
  1089. if Life < 1 then ArgError('SLNDepreciation');
  1090. Result := (Cost - Salvage) / Life
  1091. end;
  1092. function SYDDepreciation(Cost, Salvage: Extended; Life, Period: Integer): Extended;
  1093. { SYD = (cost - salvage) * (life - period + 1) / (life*(life + 1)/2) }
  1094. { Note: life*(life+1)/2 = 1+2+3+...+life "sum of years"
  1095. The depreciation factor varies from life/sum_of_years in first period = 1
  1096. downto 1/sum_of_years in last period = life.
  1097. Total depreciation over life is cost-salvage.}
  1098. var
  1099. X1, X2: Extended;
  1100. begin
  1101. Result := 0;
  1102. if (Period < 1) or (Life < Period) or (Cost <= Salvage) then Exit;
  1103. X1 := 2 * (Life - Period + 1);
  1104. X2 := Life * (Life + 1);
  1105. Result := (Cost - Salvage) * X1 / X2
  1106. end;
  1107. { Discounted cash flow functions. }
  1108. function InternalRateOfReturn(Guess: Extended; const CashFlows: array of Double): Extended;
  1109. {
  1110. Use Newton's method to solve NPV = 0, where NPV is a polynomial in
  1111. x = 1/(1+rate). Split the coefficients into negative and postive sets:
  1112. neg + pos = 0, so pos = -neg, so -neg/pos = 1
  1113. Then solve:
  1114. log(-neg/pos) = 0
  1115. Let t = log(1/(1+r) = -LnXP1(r)
  1116. then r = exp(-t) - 1
  1117. Iterate on t, then use the last equation to compute r.
  1118. }
  1119. var
  1120. T, Y: Extended;
  1121. Poly: TPoly;
  1122. K, Count: Integer;
  1123. function ConditionP(const CashFlows: array of Double): Integer;
  1124. { Guarantees existence and uniqueness of root. The sign of payments
  1125. must change exactly once, the net payout must be always > 0 for
  1126. first portion, then each payment must be >= 0.
  1127. Returns: 0 if condition not satisfied, > 0 if condition satisfied
  1128. and this is the index of the first value considered a payback. }
  1129. var
  1130. X: Double;
  1131. I, K: Integer;
  1132. begin
  1133. K := High(CashFlows);
  1134. while (K >= 0) and (CashFlows[K] >= 0.0) do Dec(K);
  1135. Inc(K);
  1136. if K > 0 then
  1137. begin
  1138. X := 0.0;
  1139. I := 0;
  1140. while I < K do begin
  1141. X := X + CashFlows[I];
  1142. if X >= 0.0 then
  1143. begin
  1144. K := 0;
  1145. Break
  1146. end;
  1147. Inc(I)
  1148. end
  1149. end;
  1150. ConditionP := K
  1151. end;
  1152. begin
  1153. InternalRateOfReturn := 0;
  1154. K := ConditionP(CashFlows);
  1155. if K < 0 then ArgError('InternalRateOfReturn');
  1156. if K = 0 then
  1157. begin
  1158. if Guess <= -1.0 then ArgError('InternalRateOfReturn');
  1159. T := -LnXP1(Guess)
  1160. end else
  1161. T := 0.0;
  1162. for Count := 1 to MaxIterations do
  1163. begin
  1164. PolyX(CashFlows, Exp(T), Poly);
  1165. if Poly.Pos <= Poly.Neg then ArgError('InternalRateOfReturn');
  1166. if (Poly.Neg >= 0.0) or (Poly.Pos <= 0.0) then
  1167. begin
  1168. InternalRateOfReturn := -1.0;
  1169. Exit;
  1170. end;
  1171. with Poly do
  1172. Y := Ln(-Neg / Pos) / (DNeg / Neg - DPos / Pos);
  1173. T := T - Y;
  1174. if RelSmall(Y, T) then
  1175. begin
  1176. InternalRateOfReturn := Exp(-T) - 1.0;
  1177. Exit;
  1178. end
  1179. end;
  1180. ArgError('InternalRateOfReturn');
  1181. end;
  1182. function NetPresentValue(Rate: Extended; const CashFlows: array of Double;
  1183. PaymentTime: TPaymentTime): Extended;
  1184. { Caution: The sign of NPV is reversed from what would be expected for standard
  1185. cash flows!}
  1186. var
  1187. rr: Extended;
  1188. I: Integer;
  1189. begin
  1190. if Rate <= -1.0 then ArgError('NetPresentValue');
  1191. rr := 1/(1+Rate);
  1192. result := 0;
  1193. for I := High(CashFlows) downto Low(CashFlows) do
  1194. result := rr * result + CashFlows[I];
  1195. if PaymentTime = ptEndOfPeriod then result := rr * result;
  1196. end;
  1197. { Annuity functions. }
  1198. {---------------
  1199. From the point of view of A, amounts received by A are positive and
  1200. amounts disbursed by A are negative (e.g. a borrower's loan repayments
  1201. are regarded by the borrower as negative).
  1202. Given interest rate r, number of periods n:
  1203. compound(r, n) = (1 + r)**n "Compounding growth factor"
  1204. annuity(r, n) = (compound(r, n)-1) / r "Annuity growth factor"
  1205. Given future value fv, periodic payment pmt, present value pv and type
  1206. of payment (start, 1 , or end of period, 0) pmtTime, financial variables satisfy:
  1207. fv = -pmt*(1 + r*pmtTime)*annuity(r, n) - pv*compound(r, n)
  1208. For fv, pv, pmt:
  1209. C := compound(r, n)
  1210. A := (1 + r*pmtTime)*annuity(r, n)
  1211. Compute both at once in Annuity2.
  1212. if C > 1E16 then A = C/r, so:
  1213. fv := meaningless
  1214. pv := -pmt*(pmtTime+1/r)
  1215. pmt := -pv*r/(1 + r*pmtTime)
  1216. else
  1217. fv := -pmt(1+r*pmtTime)*A - pv*C
  1218. pv := (-pmt(1+r*pmtTime)*A - fv)/C
  1219. pmt := (-pv*C-fv)/((1+r*pmtTime)*A)
  1220. ---------------}
  1221. function PaymentParts(Period, NPeriods: Integer; Rate, PresentValue,
  1222. FutureValue: Extended; PaymentTime: TPaymentTime; var IntPmt: Extended):
  1223. Extended;
  1224. var
  1225. Crn:extended; { =Compound(Rate,NPeriods) }
  1226. Crp:extended; { =Compound(Rate,Period-1) }
  1227. Arn:extended; { =AnnuityF(Rate,NPeriods) }
  1228. begin
  1229. if Rate <= -1.0 then ArgError('PaymentParts');
  1230. Crp:=Compound(Rate,Period-1);
  1231. Arn:=Annuity2(Rate,NPeriods,PaymentTime,Crn);
  1232. IntPmt:=(FutureValue*(Crp-1)-PresentValue*(Crn-Crp))/Arn;
  1233. PaymentParts:=(-FutureValue-PresentValue)*Crp/Arn;
  1234. end;
  1235. function FutureValue(Rate: Extended; NPeriods: Integer; Payment, PresentValue:
  1236. Extended; PaymentTime: TPaymentTime): Extended;
  1237. var
  1238. Annuity, CompoundRN: Extended;
  1239. begin
  1240. if Rate <= -1.0 then ArgError('FutureValue');
  1241. Annuity := Annuity2(Rate, NPeriods, PaymentTime, CompoundRN);
  1242. if CompoundRN > 1.0E16 then ArgError('FutureValue');
  1243. FutureValue := -Payment * Annuity - PresentValue * CompoundRN
  1244. end;
  1245. function InterestPayment(Rate: Extended; Period, NPeriods: Integer; PresentValue,
  1246. FutureValue: Extended; PaymentTime: TPaymentTime): Extended;
  1247. var
  1248. Crp:extended; { compound(rate,period-1)}
  1249. Crn:extended; { compound(rate,nperiods)}
  1250. Arn:extended; { annuityf(rate,nperiods)}
  1251. begin
  1252. if (Rate <= -1.0)
  1253. or (Period < 1) or (Period > NPeriods) then ArgError('InterestPayment');
  1254. Crp:=Compound(Rate,Period-1);
  1255. Arn:=Annuity2(Rate,Nperiods,PaymentTime,Crn);
  1256. InterestPayment:=(FutureValue*(Crp-1)-PresentValue*(Crn-Crp))/Arn;
  1257. end;
  1258. function InterestRate(NPeriods: Integer;
  1259. Payment, PresentValue, FutureValue: Extended; PaymentTime: TPaymentTime): Extended;
  1260. {
  1261. Given:
  1262. First and last payments are non-zero and of opposite signs.
  1263. Number of periods N >= 2.
  1264. Convert data into cash flow of first, N-1 payments, last with
  1265. first < 0, payment > 0, last > 0.
  1266. Compute the IRR of this cash flow:
  1267. 0 = first + pmt*x + pmt*x**2 + ... + pmt*x**(N-1) + last*x**N
  1268. where x = 1/(1 + rate).
  1269. Substitute x = exp(t) and apply Newton's method to
  1270. f(t) = log(pmt*x + ... + last*x**N) / -first
  1271. which has a unique root given the above hypotheses.
  1272. }
  1273. var
  1274. X, Y, Z, First, Pmt, Last, T, ET, EnT, ET1: Extended;
  1275. Count: Integer;
  1276. Reverse: Boolean;
  1277. function LostPrecision(X: Extended): Boolean;
  1278. asm
  1279. XOR EAX, EAX
  1280. MOV BX,WORD PTR X+8
  1281. INC EAX
  1282. AND EBX, $7FF0
  1283. JZ @@1
  1284. CMP EBX, $7FF0
  1285. JE @@1
  1286. XOR EAX,EAX
  1287. @@1:
  1288. end;
  1289. begin
  1290. Result := 0;
  1291. if NPeriods <= 0 then ArgError('InterestRate');
  1292. Pmt := Payment;
  1293. if PaymentTime = ptEndOfPeriod then
  1294. begin
  1295. X := PresentValue;
  1296. Y := FutureValue + Payment
  1297. end
  1298. else
  1299. begin
  1300. X := PresentValue + Payment;
  1301. Y := FutureValue
  1302. end;
  1303. First := X;
  1304. Last := Y;
  1305. Reverse := False;
  1306. if First * Payment > 0.0 then
  1307. begin
  1308. Reverse := True;
  1309. T := First;
  1310. First := Last;
  1311. Last := T
  1312. end;
  1313. if first > 0.0 then
  1314. begin
  1315. First := -First;
  1316. Pmt := -Pmt;
  1317. Last := -Last
  1318. end;
  1319. if (First = 0.0) or (Last < 0.0) then ArgError('InterestRate');
  1320. T := 0.0; { Guess at solution }
  1321. for Count := 1 to MaxIterations do
  1322. begin
  1323. EnT := Exp(NPeriods * T);
  1324. if {LostPrecision(EnT)} ent=(ent+1) then
  1325. begin
  1326. Result := -Pmt / First;
  1327. if Reverse then
  1328. Result := Exp(-LnXP1(Result)) - 1.0;
  1329. Exit;
  1330. end;
  1331. ET := Exp(T);
  1332. ET1 := ET - 1.0;
  1333. if ET1 = 0.0 then
  1334. begin
  1335. X := NPeriods;
  1336. Y := X * (X - 1.0) / 2.0
  1337. end
  1338. else
  1339. begin
  1340. X := ET * (Exp((NPeriods - 1) * T)-1.0) / ET1;
  1341. Y := (NPeriods * EnT - ET - X * ET) / ET1
  1342. end;
  1343. Z := Pmt * X + Last * EnT;
  1344. Y := Ln(Z / -First) / ((Pmt * Y + Last * NPeriods *EnT) / Z);
  1345. T := T - Y;
  1346. if RelSmall(Y, T) then
  1347. begin
  1348. if not Reverse then T := -T;
  1349. InterestRate := Exp(T)-1.0;
  1350. Exit;
  1351. end
  1352. end;
  1353. ArgError('InterestRate');
  1354. end;
  1355. function NumberOfPeriods(Rate, Payment, PresentValue, FutureValue: Extended;
  1356. PaymentTime: TPaymentTime): Extended;
  1357. { If Rate = 0 then nper := -(pv + fv) / pmt
  1358. else cf := pv + pmt * (1 + rate*pmtTime) / rate
  1359. nper := LnXP1(-(pv + fv) / cf) / LnXP1(rate) }
  1360. var
  1361. PVRPP: Extended; { =PV*Rate+Payment } {"initial cash flow"}
  1362. T: Extended;
  1363. begin
  1364. if Rate <= -1.0 then ArgError('NumberOfPeriods');
  1365. {whenever both Payment and PaymentTime are given together, the PaymentTime has the effect
  1366. of modifying the effective Payment by the interest accrued on the Payment}
  1367. if ( PaymentTime=ptStartOfPeriod ) then
  1368. Payment:=Payment*(1+Rate);
  1369. {if the payment exactly matches the interest accrued periodically on the
  1370. presentvalue, then an infinite number of payments are going to be
  1371. required to effect a change from presentvalue to futurevalue. The
  1372. following catches that specific error where payment is exactly equal,
  1373. but opposite in sign to the interest on the present value. If PVRPP
  1374. ("initial cash flow") is simply close to zero, the computation will
  1375. be numerically unstable, but not as likely to cause an error.}
  1376. PVRPP:=PresentValue*Rate+Payment;
  1377. if PVRPP=0 then ArgError('NumberOfPeriods');
  1378. { 6.1E-5 approx= 2**-14 }
  1379. if ( EAbs(Rate)<6.1E-5 ) then
  1380. Result:=-(PresentValue+FutureValue)/PVRPP
  1381. else
  1382. begin
  1383. {starting with the initial cash flow, each compounding period cash flow
  1384. should result in the current value approaching the final value. The
  1385. following test combines a number of simultaneous conditions to ensure
  1386. reasonableness of the cashflow before computing the NPER.}
  1387. T:= -(PresentValue+FutureValue)*Rate/PVRPP;
  1388. if T<=-1.0 then ArgError('NumberOfPeriods');
  1389. Result := LnXP1(T) / LnXP1(Rate)
  1390. end;
  1391. NumberOfPeriods:=Result;
  1392. end;
  1393. function Payment(Rate: Extended; NPeriods: Integer; PresentValue, FutureValue:
  1394. Extended; PaymentTime: TPaymentTime): Extended;
  1395. var
  1396. Annuity, CompoundRN: Extended;
  1397. begin
  1398. if Rate <= -1.0 then ArgError('Payment');
  1399. Annuity := Annuity2(Rate, NPeriods, PaymentTime, CompoundRN);
  1400. if CompoundRN > 1.0E16 then
  1401. Payment := -PresentValue * Rate / (1 + Integer(PaymentTime) * Rate)
  1402. else
  1403. Payment := (-PresentValue * CompoundRN - FutureValue) / Annuity
  1404. end;
  1405. function PeriodPayment(Rate: Extended; Period, NPeriods: Integer;
  1406. PresentValue, FutureValue: Extended; PaymentTime: TPaymentTime): Extended;
  1407. var
  1408. Junk: Extended;
  1409. begin
  1410. if (Rate <= -1.0) or (Period < 1) or (Period > NPeriods) then ArgError('PeriodPayment');
  1411. PeriodPayment := PaymentParts(Period, NPeriods, Rate, PresentValue,
  1412. FutureValue, PaymentTime, Junk);
  1413. end;
  1414. function PresentValue(Rate: Extended; NPeriods: Integer; Payment, FutureValue:
  1415. Extended; PaymentTime: TPaymentTime): Extended;
  1416. var
  1417. Annuity, CompoundRN: Extended;
  1418. begin
  1419. if Rate <= -1.0 then ArgError('PresentValue');
  1420. Annuity := Annuity2(Rate, NPeriods, PaymentTime, CompoundRN);
  1421. if CompoundRN > 1.0E16 then
  1422. PresentValue := -(Payment / Rate * Integer(PaymentTime) * Payment)
  1423. else
  1424. PresentValue := (-Payment * Annuity - FutureValue) / CompoundRN
  1425. end;
  1426. end.