MathgeomGLS
259 строк · 8.9 Кб
1{---------------------------------------------------------------------------}
2{ }
3{ File: Velthuis.ExactFloatStrings.pas }
4{ Function: Routines to generate strings that contain the exact values }
5{ of Singles, Doubles or Extendeds. }
6{ Language: Delphi version XE3 or later }
7{ Author: Rudy Velthuis }
8{ Copyright: (c) 2015, Rudy Velthuis }
9{ Notes: Requires the Velthuis.BigIntegers unit }
10{ Requires record helpers for intrinsic types }
11{ }
12{ License: Redistribution and use in source and binary forms, with or }
13{ without modification, are permitted provided that the }
14{ following conditions are met: }
15{ }
16{ * Redistributions of source code must retain the above }
17{ copyright notice, this list of conditions and the following }
18{ disclaimer. }
19{ * Redistributions in binary form must reproduce the above }
20{ copyright notice, this list of conditions and the following }
21{ disclaimer in the documentation and/or other materials }
22{ provided with the distribution. }
23{ }
24{ Disclaimer: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" }
25{ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT }
26{ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND }
27{ FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO }
28{ EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE }
29{ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, }
30{ OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, }
31{ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, }
32{ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED }
33{ AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT }
34{ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) }
35{ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF }
36{ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. }
37{ }
38{---------------------------------------------------------------------------}
39
40unit Velthuis.ExactFloatStrings;
41
42interface
43
44{$IF CompilerVersion >= 24.0}
45{$LEGACYIFEND ON}
46{$IFEND}
47
48{$IF SizeOf(Extended) > SizeOf(Double)}
49{$DEFINE HASEXTENDED}
50{$IFEND}
51
52uses
53System.SysUtils;
54
55{$IFDEF HASEXTENDED}
56function ExactString(const F: Extended): string; overload;
57{$ENDIF}
58function ExactString(const F: Double): string; overload;
59function ExactString(const F: Single): string; overload;
60
61implementation
62
63uses
64Velthuis.BigIntegers,
65Velthuis.FloatUtils,
66System.Math;
67
68// BigIntegers are required to either multiply the mantissa by powers of 5 or by powers of 2 and to
69// generate a string from the resulting BigInteger.
70// Record helpers for intrinsics are used to get info out of the floating point types, e.g. IsNan, Mantissa, etc.
71
72{$IFDEF HASEXTENDED}
73function ExactString(const F: Extended): string;
74var
75Mantissa: UInt64;
76Exponent: Integer;
77Sign: Boolean;
78BigInt: BigInteger;
79DecimalPoint: Integer;
80Len: Integer;
81begin
82if System.Math.IsNaN(F) then
83Exit('NaN')
84else if IsNegativeInfinity(F) then
85Exit('NegInfinity')
86else if IsPositiveInfinity(F) then
87Exit('Infinity');
88
89Mantissa := GetSignificand(F);
90if Mantissa = 0 then
91Exit('0');
92
93Exponent := GetExponent(F) - 63;
94Sign := System.Math.Sign(F) < 0;
95
96while not Odd(Mantissa) do
97begin
98Mantissa := Mantissa shr 1;
99Inc(Exponent);
100end;
101
102BigInt := Mantissa;
103
104DecimalPoint := 0;
105if Exponent < 0 then
106begin
107// BigInt must repeatedly be divided by 2.
108// This isn't done directly: On each iteration, BigInt is multiplied by 5 and then the decimal point is moved one
109// position to the left, which is equivalent to dividing by 10. This is done in one fell swoop, using Pow().
110BigInt := BigInt * BigInteger.Pow(5, -Exponent);
111DecimalPoint := -Exponent;
112end
113else
114// BigInt must repeatedly be multipied by 2. This is done in one go, by shifting the BigInteger left by Exponent.
115BigInt := BigInt shl Exponent;
116
117Result := BigInt.ToString;
118Len := Length(Result);
119
120// Now we insert zeroes and the decimal point into the plain big integer value to get a nice output.
121
122if DecimalPoint = 0 then
123Result := Result // e.g. 123.0
124else if DecimalPoint >= Len then
125Result := '0.' + StringOfChar('0', DecimalPoint - Len) + Result // e.g. 0.00123
126else
127Result := Copy(Result, 1, Len - DecimalPoint) + '.' + Copy(Result, Len - DecimalPoint + 1, Len); // e.g. 12.3
128
129if Sign then
130Result := '-' + Result;
131end;
132{$ENDIF}
133
134function ExactString(const F: Double): string;
135var
136Mantissa: UInt64;
137Exponent: Integer;
138Sign: Boolean;
139BigInt: BigInteger;
140DecimalPoint: Integer;
141Len: Integer;
142begin
143if System.Math.IsNaN(F) then
144Exit('NaN')
145else if IsNegativeInfinity(F) then
146Exit('NegInfinity')
147else if IsPositiveInfinity(F) then
148Exit('Infinity');
149
150Mantissa := GetSignificand(F);
151if Mantissa = 0 then
152Exit('0');
153
154Exponent := GetExponent(F) - 52;
155Sign := System.Math.Sign(F) < 0;
156if IsDenormal(F) then
157Mantissa := Mantissa and (UInt64(-1) shr 12);
158
159while not Odd(Mantissa) do
160begin
161Mantissa := Mantissa shr 1;
162Inc(Exponent);
163end;
164
165BigInt := Mantissa;
166
167DecimalPoint := 0;
168if Exponent < 0 then
169begin
170// BigInt must be repeatedly divided by 2.
171// This isn't done directly: On each iteration, BigInt is multiplied by 5 and then the decimal point is moved one
172// position to the left, which is equivalent to dividing by 10. This is done in one fell swoop, using Pow().
173BigInt := BigInt * BigInteger.Pow(5, -Exponent);
174DecimalPoint := -Exponent;
175end
176else
177// BigInt must repeatedly be multipied by 2. This is done in one go, by shifting the BigInteger left.
178BigInt := BigInt shl Exponent;
179
180Result := BigInt.ToString;
181Len := Length(Result);
182
183// Now we insert zeroes and the decimal point into the plain big integer value to get a nice output.
184
185if DecimalPoint = 0 then
186Result := Result // e.g. 123.0
187else if DecimalPoint >= Len then
188Result := '0.' + StringOfChar('0', DecimalPoint - Len) + Result // e.g. 0.00123
189else
190Result := Copy(Result, 1, Len - DecimalPoint) + '.' + Copy(Result, Len - DecimalPoint + 1, Len); // e.g. 12.3
191
192if Sign then
193Result := '-' + Result;
194end;
195
196function ExactString(const F: Single): string;
197var
198Mantissa: UInt32;
199Exponent: Integer;
200Sign: Boolean;
201BigInt: BigInteger;
202DecimalPoint: Integer;
203Len: Integer;
204begin
205if System.Math.IsNan(F) then
206Exit('NaN')
207else if IsNegativeInfinity(F) then
208Exit('NegInfinity')
209else if IsPositiveInfinity(F) then
210Exit('Infinity');
211
212Mantissa := GetSignificand(F);
213if Mantissa = 0 then
214Exit('0');
215
216Exponent := GetExponent(F) - 23;
217Sign := System.Math.Sign(F) < 0;
218if IsDenormal(F) then
219Mantissa := Mantissa and $7FFFFF;
220
221while not Odd(Mantissa) do
222begin
223Mantissa := Mantissa shr 1;
224Inc(Exponent);
225end;
226
227BigInt := Mantissa;
228
229DecimalPoint := 0;
230if Exponent < 0 then
231begin
232// BigInt must be repeatedly divided by 2.
233// This isn't done directly: On each iteration, BigInt is multiplied by 5 and then the decimal point is moved one
234// position to the left, which is equivalent to dividing by 10. This is done in one fell swoop, using Pow().
235BigInt := BigInt * BigInteger.Pow(5, -Exponent);
236DecimalPoint := -Exponent;
237end
238else
239// BigInt must repeatedly be multipied by 2. This is done in one go, by shifting the BigInteger left.
240BigInt := BigInt shl Exponent;
241
242Result := BigInt.ToString;
243Len := Length(Result);
244
245// Now we insert zeroes and the decimal point into the plain big integer value to get a nice output.
246
247if DecimalPoint = 0 then
248Result := Result // e.g. 123.0
249else if DecimalPoint >= Len then
250Result := '0.' + StringOfChar('0', DecimalPoint - Len) + Result // e.g. 0.00123
251else
252Result := Copy(Result, 1, Len - DecimalPoint) + '.' + Copy(Result, Len - DecimalPoint + 1, Len); // e.g. 12.3
253
254if Sign then
255Result := '-' + Result;
256
257end;
258
259end.
260