root/trunk/meta/conv.d

Revision 160, 10.3 kB (checked in by Don Clugston, 9 years ago)

Many accumulated changes to 'meta'. The most interesting is the one in feqtest, which tests floating point numbers for equality to the number of decimal places given in a string.

Line 
1 /*  Compile-time conversions between strings and numeric values.
2   *
3   * In most cases, it's not possible to overload template value parameters,
4   * so most of these functions have unique names.
5   * Compile with -version=testmeta to run unit tests.
6   * Author: Don Clugston. License: Public domain.
7   */
8 /*
9 Normally, a function which processes the first part of a string
10 would return the part which was not consumed (the 'tail').
11 This doesn't work very well for D templates, so for such functions,
12 we supply a seperate metafunction with the suffix 'Consumed' which returns
13 the  number of characters consumed. (This is the same as the index of the first
14 character in the string which was not consumed).
15 This results in some code duplication, but seems to results in a much cleaner design.
16 */
17  
18 module meta.conv;
19 private import meta.math;
20 private import meta.string;
21 private import meta.ctype;
22
23 template decimaldigit(int n) { const char [] decimaldigit = "0123456789"[n..n+1]; }
24 template hexdigit(int n) { const char [] hexdigit = "0123456789ABCDEF"[n..n+1]; }
25
26 /* *****************************************************
27  *  char [] itoa!(long n);
28  */
29 template itoa(long n)
30 {
31     static if (n<0)
32         const char [] itoa = "-" ~ itoa!(-n);
33     else static if (n<10L)
34         const char [] itoa = decimaldigit!(n);
35     else
36         const char [] itoa = itoa!(n/10L) ~ decimaldigit!(n%10L);
37 }
38
39 template toHexString(ulong n)
40 {
41     static if (n<16L)
42         const char [] toHexString = hexdigit!(n);
43     else
44         const char [] toHexString = toHexString!(n >> 4) ~ hexdigit!(n&0xF);
45 }
46
47 /* *****************************************************
48  *  long atoi!(char [] s);
49  */
50 template atoi(char [] s, long result=0, int indx=0)
51 {
52     static if (s.length==indx)
53         const long atoi = result;
54     else static if (indx==0 && s[indx]=='-')
55         const long atoi = - atoi!(s, 0, 1);
56     else static if (!isdigit!( (s[indx]) ) )
57         const long atoi = result;
58     else
59         const long atoi = atoi!(s, result * 10 + s[indx]-'0', indx+1);
60 }
61
62 /* *****************************************************
63  *  the number of characters in s[] which would be 'consumed' by atoi!().
64  */
65 template atoiConsumed(char [] s, int indx=0)
66 {
67     static if(s.length==indx)
68         const int atoiConsumed = indx;
69     else static if ((indx==0 && s[indx]=='-') || isdigit!( (s[indx]) ) )
70         const int atoiConsumed = atoiConsumed!(s,indx + 1);
71     else    // invalid character
72         const int atoiConsumed = indx;
73 }
74
75 private enum  EFloatParseState { START=0, GOTSIGN, WAITDOT, WAITEXP=3, GOTEXPCHAR, GOTEXPSIGN, EXPDIG };
76
77
78 // Like atoi!(), except that internal underscores are allowable
79 template parseInt(char [] s, long result=0, EFloatParseState state =0 /* start*/)
80 {
81     static if (s.length==0)
82         const long parseInt = result;
83     else static if (state == EFloatParseState.START && s[0]=='-')
84         const long parseInt = - parseInt!(s[1..$], 0, GOTSIGN);
85     else static if (state == EFloatParseState.WAITDOT && s[0]=='_')
86         const long parseInt = parseInt!(s[1..$], result, state);
87     else static if (isdigit!( (s[0]) ) )
88         const long parseInt = parseInt!(s[1..$], result*10 + s[0]-'0', WAITDOT);
89 }
90
91 // Returns the value of the exponent.
92 // must be of the form "" or  "e-543" or "E+2_453" etc
93 template parseExponent(char [] s, int result = 0, EFloatParseState state = EFloatParseState.WAITEXP)
94 {
95     static if (s.length == 0) {
96         // an empty string is acceptable
97         static if (state == EFloatParseState.WAITEXP || state == EFloatParseState.EXPDIG)
98             const int parseExponent = result;
99         else {
100             pragma(msg, "Error: No exponent found in floating-point literal.");
101             static assert(0);
102         }
103     } else static if (state == EFloatParseState.WAITEXP && (s[0]=='e' || s[0]=='E') )
104         const int parseExponent = parseExponent!(s[1..$], 0, EFloatParseState.GOTEXPCHAR);
105     else static if (state == EFloatParseState.GOTEXPCHAR && s[0]=='-')
106         const int parseExponent = -parseExponent!(s[1..$], 0, EFloatParseState.GOTEXPSIGN);
107     else static if (state == EFloatParseState.GOTEXPCHAR && s[0]=='+')
108         const int parseExponent = parseExponent!(s[1..$], 0, EFloatParseState.GOTEXPSIGN);
109     else static if (state == EFloatParseState.EXPDIG && s[0]=='_')
110         // embedded underscores are allowed after the first digit
111         const int parseExponent = parseExponent!(s[1..$], result, state);
112     else static if (isdigit!( (s[0]) ) )
113        const int parseExponent = parseExponent!(s[1..$], result*10 + (s[0]-'0'), EFloatParseState.EXPDIG);   
114     else {
115         pragma(msg, "Error: Invalid characters found in floating-point literal.");
116         static assert(0);
117     }
118 }
119
120 // parse a %f-style floating-point number, returning the result as a real.
121 // A minus sign is allowed as the first character.
122 // Embedded underscores are allowed any time after the first digit.
123 template parseMantissa(char [] s, EFloatParseState state = EFloatParseState.START, real result=0.0L)
124 {
125     static if (s.length==0)
126         const real parseMantissa = result;
127     else static if (state==EFloatParseState.START && s[0]=='-')
128         const real parseMantissa = - parseMantissa!(s[1..$], EFloatParseState.GOTSIGN, 0.0L);
129     else static if (state==EFloatParseState.WAITDOT && s[0]=='.')
130         const real parseMantissa = result + parseMantissa!(s[1..$], EFloatParseState.WAITEXP, 0.0L);
131     else static if (state!=EFloatParseState.START  && state!=EFloatParseState.GOTSIGN && s[0]=='_')
132         // allow embedded underscores, but not before the first digit
133         const real parseMantissa = parseMantissa!(s[1..$], state, result);
134     else static if (isdigit!( (s[0]) )) {
135         static if (state == EFloatParseState.WAITEXP)
136             // fractional part
137             const real parseMantissa = result + (s[0]-'0')/10.0L + parseMantissa!(s[1..$], state, 0.0L) /10.0L;
138         else
139             const real parseMantissa = parseMantissa!(s[1..$], EFloatParseState.WAITDOT, result*10.0L + (s[0]-'0') );
140     } else static if (state == EFloatParseState.WAITDOT || state == EFloatParseState.WAITEXP)
141             const real parseMantissa = result;
142     else {
143         pragma(msg, "Error: No digits found in floating-point literal.");
144         static assert(0);
145     }
146 }
147
148 template parseMantissaConsumed(char [] s, EFloatParseState state = EFloatParseState.START)
149 {
150     static if (s.length == 0)
151         const int parseMantissaConsumed = 0;
152     else static if (state==EFloatParseState.START && s[0]=='-')
153         const int parseMantissaConsumed = 1 + parseMantissaConsumed!(s[1..$], EFloatParseState.GOTSIGN);
154     else static if (state==EFloatParseState.WAITDOT && s[0]=='.')
155         const int parseMantissaConsumed = 1 + parseMantissaConsumed!(s[1..$], EFloatParseState.WAITEXP);
156     else static if (state!=EFloatParseState.START  && state!=EFloatParseState.GOTSIGN && s[0]=='_')
157         // allow embedded underscores, but not before the first digit
158         const int parseMantissaConsumed = 1 + parseMantissaConsumed!(s[1..$], state);
159     else static if (isdigit!( (s[0]) ))
160         const int parseMantissaConsumed = 1 + parseMantissaConsumed!(s[1..$],
161             (state == EFloatParseState.WAITEXP) ? state : EFloatParseState.WAITDOT );
162     else // found first offending character.
163         //static assert(state != EFloatParseState.START && state!= EFloatParseState.GOTSIGN);
164         const int parseMantissaConsumed = 0;
165 }
166
167
168 // accepts %f or %e format.
169 template atof(char [] s)
170 {
171     const real atof = parseMantissa!(s) * meta.math.pow!(10.0L,
172             parseExponent!(s[parseMantissaConsumed!(s)..$]));
173 }
174
175 // returns number of decimal places in s.
176 // s must be a valid floating-point literal
177 template decimalplaces(char [] s, bool gotDot = false)
178 {
179     static if (s.length==0)
180         const int decimalplaces = 0;
181     else static if ( !gotDot && s[0]=='.')
182         const int decimalplaces = decimalplaces!(s[1..$], true);
183     else static if (s[0]=='_' || !gotDot) {
184         const int decimalplaces = decimalplaces!(s[1..$], gotDot);
185     } else {
186         static if (isdigit!( (s[0]) ) )
187             const int decimalplaces = 1 + decimalplaces!(s[1..$], true);
188         else // we've finished
189             const int decimalplaces = 0;
190     }
191 }
192
193
194 //------------------------------------------------
195 // Given a number x, where 0<= x <1,
196 // returns the first 'maxdigs' digits after the decimal point.
197 template afterdec(real x, int maxdigs=real.dig)
198 {
199     static if (maxdigs==0 || x==0) const char [] afterdec = "";
200   else const char [] afterdec = decimaldigit!(cast(int)(x*10)) ~ afterdec!(x*10-cast(int)(x*10), maxdigs-1);
201 }
202
203 /* *****************************************************
204  *  char [] fcvt!(real x)
205  *  Convert a real number x to %f format
206  */
207 template fcvt(real x)
208 {
209     static if (x<0) {
210         const real fcvt = "-" ~ .fcvt!(-x);
211     } else static if (x==cast(long)x) {
212         const char [] fcvt = itoa!(cast(long)x);
213     } else {
214         const char [] fcvt = itoa!(cast(long)x) ~ "." ~ chomp!(afterdec!(x - cast(long)x), '0');
215     }
216 }
217
218 template itoaWithSign(long x)
219 {
220   static if (x>=0) const char [] itoaWithSign = "+" ~ itoa!(x);
221   else const char [] itoaWithSign = itoa!(x);
222 }
223
224 /* *****************************************************
225  * char [] pcvt!(real x)
226  * Convert a real number x to %a format, eg 0x1.ABCDp+30
227  */
228 template pcvt(real x)
229 {
230   static if (isnan!(x)) const char [] pcvt = "nan";
231   else static if (x<0) const char [] pcvt =  "-" ~ .pcvt!(-x);
232   else static if (x==real.infinity) const char [] pcvt = "inf";
233   else const char [] pcvt = "0x1." ~ toHexString!(cast(ulong)(0x1000000 *(binaryMantissa!(x)-1.0)) ) ~ "p" ~ itoaWithSign!(binaryExponent!(x));
234 }
235
236 version(testmeta) {
237
238
239     static assert(parseMantissa!("3.34") == 3.34);
240     static assert(parseMantissa!("-548_29.317_1abc") == -548_29.317_1);
241     static assert( isPositiveZero!(parseMantissa!("0.0")));
242     static assert( isNegativeZero!(parseMantissa!("-0.0")));
243     static assert(parseMantissaConsumed!("-31_4.3252e34") == 10);
244     static assert(parseMantissaConsumed!("_23e112") == 0);
245
246  static assert( pcvt!(0x1.12345p954L) == "0x1.123450p+954" );
247  static assert( fcvt!(12.345) == "12.345" );
248
249  static assert( atoi!("3580abc")==3580);
250  static assert( atoi!("-0326")==-326);
251  static assert( atoiConsumed!("325827wip")==6);
252  static assert( atoiConsumed!("abc")==0);
253 }
Note: See TracBrowser for help on using the browser.