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