1 /*******************************************************************************
2
3 Functions for generating thousands separated string representations of a
4 number.
5
6 Usage:
7
8 ---
9
10 import ocean.text.util.DigitGrouping;
11
12 // Number to convert
13 const number = 12345678;
14
15 // Generating a thousands separated string.
16 char[] number_as_string;
17 DigitGrouping.format(number, number_as_string);
18
19 // Checking how many characters would be required for a thousands
20 // separated number.
21 cont max_len = 10;
22 assert(DigitGrouping.length(number) <= max_len);
23
24 ---
25
26 Copyright:
27 Copyright (c) 2009-2016 dunnhumby Germany GmbH.
28 All rights reserved.
29
30 License:
31 Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
32 Alternatively, this file may be distributed under the terms of the Tango
33 3-Clause BSD License (see LICENSE_BSD.txt for details).
34
35 *******************************************************************************/
36
37 module ocean.text.util.DigitGrouping;
38
39
40
41
42 import ocean.meta.types.Qualifiers;
43
44 import ocean.core.TypeConvert;
45
46 import ocean.core.Array;
47
48 import ocean.text.util.MetricPrefix;
49
50 import ocean.meta.traits.Basic;
51
52 import ocean.text.convert.Formatter;
53
54
55
56 /*******************************************************************************
57
58 Digit grouping class -- just a container for static functions.
59
60 *******************************************************************************/
61
62 public class DigitGrouping
63 {
64 private alias typeof(this) This;
65
66
67 /***************************************************************************
68
69 Calculates the number of characters in the string representation of a
70 thousands separated number.
71
72 Note: this method is faster than generating the string then checking its
73 .length property.
74
75 Params:
76 T = type of number
77 num = number to work out length of
78
79 Returns:
80 number of characters in the string representation of the thousands
81 separated number
82
83 ***************************************************************************/
84
85 public static size_t length ( T ) ( T num )
86 {
87 static assert(isIntegerType!(T), This.stringof ~ ".length - only works with integer types");
88
89 bool negative = num < 0;
90 if ( negative ) num = -num;
91
92 // Calculate the number of digits in the number.
93 size_t len = 1; // every number has at least 1 digit
94 do
95 {
96 num /= 10;
97 if ( num > 0 )
98 {
99 len++;
100 }
101 }
102 while ( num > 0);
103
104 // Extra characters for any thousands separating commas required.
105 if ( len > 3 )
106 {
107 len += (len - 1) / 3;
108 }
109
110 // An extra character for a minus sign.
111 if ( negative ) len++;
112
113 return len;
114 }
115
116
117 /***************************************************************************
118
119 Formats a number to a string, with comma separation every 3 digits
120
121 Params:
122 T = type of number
123 num = number to be formatted
124 output = string in which to store the formatted number
125
126 Returns:
127 formatted string
128
129 ***************************************************************************/
130
131 public static mstring format ( T ) ( T num, ref mstring output )
132 {
133 static assert(isIntegerType!(T), This.stringof ~ ".format - only works with integer types");
134
135 output.length = 0;
136
137 char[20] string_buf; // 20 characters is enough to store ulong.max
138 size_t layout_pos;
139
140 void layoutSink ( cstring s )
141 {
142 string_buf[layout_pos .. layout_pos + s.length] = s[];
143 layout_pos += s.length;
144 }
145
146 // Format number into a string
147 sformat(&layoutSink, "{}", num);
148 mstring num_as_string = string_buf[0.. layout_pos];
149
150 bool comma;
151 size_t left = 0;
152 size_t right = left + 3;
153 size_t first_comma;
154
155 // Handle negative numbers
156 if ( num_as_string[0] == '-' )
157 {
158 output.append("-"[]);
159 num_as_string = num_as_string[1..$];
160 }
161
162 // Find position of first comma
163 if ( num_as_string.length > 3 )
164 {
165 comma = true;
166 first_comma = num_as_string.length % 3;
167
168 if ( first_comma > 0 )
169 {
170 right = first_comma;
171 }
172 }
173
174 // Copy chunks of the formatted number into the destination string, with commas
175 do
176 {
177 if ( right >= num_as_string.length )
178 {
179 right = num_as_string.length;
180 comma = false;
181 }
182
183 mstring digits = num_as_string[left..right];
184 if ( comma )
185 {
186 output.append(digits, ","[]);
187 }
188 else
189 {
190 output.append(digits);
191 }
192
193 left = right;
194 right = left + 3;
195 }
196 while( left < num_as_string.length );
197
198 return output;
199 }
200 }
201
202 version (unittest)
203 {
204 import ocean.core.Test : test;
205 }
206
207 unittest
208 {
209 test!("==")(DigitGrouping.length(-100000), "-100,000".length);
210 test!("==")(DigitGrouping.length( -10000), "-10,000".length);
211 test!("==")(DigitGrouping.length( -1000), "-1,000".length);
212 test!("==")(DigitGrouping.length( -100), "-100".length);
213 test!("==")(DigitGrouping.length( -10), "-10".length);
214 test!("==")(DigitGrouping.length( -0), "0".length);
215 test!("==")(DigitGrouping.length( 0), "0".length);
216 test!("==")(DigitGrouping.length( 10), "10".length);
217 test!("==")(DigitGrouping.length( 100), "100".length);
218 test!("==")(DigitGrouping.length( 1000), "1,000".length);
219 test!("==")(DigitGrouping.length( 10000), "10,000".length);
220 test!("==")(DigitGrouping.length( 100000), "100,000".length);
221 test!("==")(DigitGrouping.length(1000000), "1,000,000".length);
222
223 mstring buf;
224
225 test!("==")(DigitGrouping.format(-100000, buf), "-100,000"[]);
226 test!("==")(DigitGrouping.format( -10000, buf), "-10,000"[]);
227 test!("==")(DigitGrouping.format( -1000, buf), "-1,000"[]);
228 test!("==")(DigitGrouping.format( -100, buf), "-100"[]);
229 test!("==")(DigitGrouping.format( -10, buf), "-10"[]);
230 test!("==")(DigitGrouping.format( -0, buf), "0"[]);
231 test!("==")(DigitGrouping.format( 0, buf), "0"[]);
232 test!("==")(DigitGrouping.format( 10, buf), "10"[]);
233 test!("==")(DigitGrouping.format( 100, buf), "100"[]);
234 test!("==")(DigitGrouping.format( 1000, buf), "1,000"[]);
235 test!("==")(DigitGrouping.format( 10000, buf), "10,000"[]);
236 test!("==")(DigitGrouping.format( 100000, buf), "100,000"[]);
237 test!("==")(DigitGrouping.format(1000000, buf), "1,000,000"[]);
238 }
239
240
241
242 /*******************************************************************************
243
244 Binary digit grouping class -- just a container for static functions.
245
246 *******************************************************************************/
247
248 public class BitGrouping
249 {
250 /***************************************************************************
251
252 Formats a number to a string, with binary prefix (K, M, T, etc) every
253 10 bits.
254
255 Params:
256 num = number to be formatted
257 output = string in which to store the formatted number
258 unit = string, describing the type of unit represented by the
259 number, to be appended after each binary prefix
260
261 Returns:
262 formatted string
263
264 ***************************************************************************/
265
266 public static mstring format ( ulong num, ref mstring output, cstring unit = null )
267 {
268 output.length = 0;
269 assumeSafeAppend(output);
270
271 if ( num == 0 )
272 {
273 sformat(output, "0{}", unit);
274 }
275 else
276 {
277 void format ( char prefix, uint order, ulong order_val )
278 {
279 if ( order_val > 0 )
280 {
281 if ( order == 0 )
282 {
283 sformat(output, "{}{}", order_val, unit);
284 }
285 else
286 {
287 sformat(output, "{}{}{} ", order_val, prefix, unit);
288 }
289 }
290 else if ( order_val == 0 && order == 0 )
291 {
292 // Get rid of the additional space that was appended.
293
294 output = output[0 .. $ - 1];
295 }
296 }
297
298 splitBinaryPrefix(num, &format);
299 }
300
301 return output;
302 }
303 }
304
305 unittest
306 {
307 mstring buf;
308
309 test!("==")(BitGrouping.format(0, buf), "0"[]);
310 test!("==")(BitGrouping.format(0, buf, "X"), "0X"[]);
311
312 test!("==")(BitGrouping.format(1000, buf), "1000"[]);
313 test!("==")(BitGrouping.format(1000, buf, "X"), "1000X"[]);
314
315 test!("==")(BitGrouping.format(1024, buf), "1K"[]);
316 test!("==")(BitGrouping.format(1024, buf, "M"), "1KM"[]);
317
318 test!("==")(BitGrouping.format(1025, buf), "1K 1"[]);
319 test!("==")(BitGrouping.format(1025, buf, "TEST"), "1KTEST 1TEST"[]);
320
321 test!("==")(BitGrouping.format(10000, buf), "9K 784"[]);
322 test!("==")(BitGrouping.format(10000, buf, "X"), "9KX 784X"[]);
323
324 test!("==")(BitGrouping.format(1000000, buf), "976K 576"[]);
325 test!("==")(BitGrouping.format(1000000, buf, "X"), "976KX 576X"[]);
326
327 test!("==")(BitGrouping.format(10000000, buf), "9M 549K 640"[]);
328 test!("==")(BitGrouping.format(10000000, buf, "X"), "9MX 549KX 640X"[]);
329
330 test!("==")(BitGrouping.format(10000000000, buf), "9G 320M 761K"[]);
331 test!("==")(BitGrouping.format(10000000000, buf, "X"), "9GX 320MX 761KX"[]);
332 }
333