1 /******************************************************************************
2 
3     Converts values into a metric representation with a scaled mantissa and a
4     decimal exponent unit prefix character.
5 
6     Copyright:
7         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
8         All rights reserved.
9 
10     License:
11         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
12         Alternatively, this file may be distributed under the terms of the Tango
13         3-Clause BSD License (see LICENSE_BSD.txt for details).
14 
15  ******************************************************************************/
16 
17 module ocean.text.util.MetricPrefix;
18 
19 
20 
21 
22 import core.stdc.math;
23 
24 import ocean.core.Verify;
25 
26 
27 /*******************************************************************************
28 
29     Metric prefix struct.
30 
31     Usage example:
32 
33     ---
34 
35         import ocean.text.util.MetricPrefix;
36 
37         // Number to display in metric prefixed mode.
38         const number = 2876873683;
39 
40         // Struct instance
41         MetricPrefix metric;
42 
43         // Calculate the binary metric prefix of the number.
44         metric.bin(number);
45 
46         // Output metric prefixed string (2.679297405Gb, in this case).
47         Stdout.formatln("{}{}b", metric.scaled, metric.prefix);
48 
49     ---
50 
51 *******************************************************************************/
52 
53 public struct MetricPrefix
54 {
55     /**************************************************************************
56 
57         Scaled mantissa; set by bin()/dec()
58 
59     **************************************************************************/
60 
61     float scaled = 0.;
62 
63     /**************************************************************************
64 
65         Metric decimal power unit prefix; set by bin()/dec()
66 
67     **************************************************************************/
68 
69     dchar prefix = ' ';
70 
71     public enum BinaryPrefixes = [' ', 'K', 'M', 'G', 'T', 'P', 'E'];
72 
73     /**************************************************************************
74 
75         Converts n into a metric-like prefixed representation, using powers of
76         1024.
77         Example: For n == 12345678 this.scaled about 11.78 and this.prefix is
78         'M'.
79 
80         Params:
81             n = number to convert
82 
83         Returns:
84             this instance
85 
86     **************************************************************************/
87 
88     typeof(&this) bin ( T : float ) ( T n )
89     {
90         this.scaled = n;
91 
92         int i;
93 
94         static if (is (T : long))
95         {
96             for (i = 0; (n > 0x400) && (i < BinaryPrefixes.length); i++)
97             {
98                 n >>= 10;
99             }
100         }
101         else
102         {
103             frexpf(n, &i);
104             i /= 10;
105         }
106 
107         this.scaled = ldexpf(this.scaled, i * -10);
108 
109         this.prefix = BinaryPrefixes[i];
110 
111         return &this;
112     }
113 
114     public enum DecimalPrefixes = [cast(wchar)'p', 'n', 'µ', 'm', ' ', 'k', 'M', 'G', 'T'];
115 
116     /**************************************************************************
117 
118         Converts n into a metric prefixed representation.
119         Example: For n == 12345678 this.scaled is about 12.35 and this.prefix is
120                  'M'.
121 
122         Params:
123             n = number to convert
124             e = input prefix: 0 = None, 1 = 'k', -1 = 'm', 2 = 'M', -2 = 'µ' etc.,
125                               up to +/- 4
126 
127         Returns:
128             this instance
129 
130     **************************************************************************/
131 
132     typeof(&this) dec ( T : float ) ( T n, int e = 0 )
133     {
134         verify (-5 < e && e < 5);
135 
136         this.scaled = n;
137 
138         int i = 4;
139 
140         if (n != 0)
141         {
142             if (n > 1)
143             {
144                 for (i += e; (n > 1000) && (i+1 < DecimalPrefixes.length); i++)
145                 {
146                     n           /= 1000;
147                     this.scaled /= 1000;
148                 }
149             }
150             else
151             {
152                 for (i += e; (n < 1) && (i-1 > 0); i--)
153                 {
154                     n           *= 1000;
155                     this.scaled *= 1000;
156                 }
157             }
158         }
159 
160         this.prefix = DecimalPrefixes[i];
161 
162         return &this;
163     }
164 }
165 
166 
167 
168 /*******************************************************************************
169 
170     Splits the given number by binary prefixes (K, M, T, etc), passing the
171     prefix character, the prefix order and the count per prefix to the output
172     delegate.
173 
174     Params:
175         n = number to split
176         output_dg = delegate which receives prefix values
177 
178     See also: BitGrouping in ocean.text.util.DigitGrouping, for a method which
179     automatically formats a split binary prefix string.
180 
181     Note that if n == 0, the output delegate will not be called.
182 
183     Usage example:
184 
185     ---
186 
187         import ocean.text.util.MetricPrefix;
188 
189         // Number to split by binary prefix.
190         const number = 2876873683;
191 
192         // Delegate which receives the split info.
193         void split ( char prefix, uint order, ulong order_val )
194         {
195             Stdout.formatln("Order {}: {}{}", order, order_val, prefix);
196         }
197 
198         // Perform the split.
199         splitBinaryPrefix(number, &split);
200 
201     ---
202 
203 *******************************************************************************/
204 
205 public void splitBinaryPrefix ( ulong n, scope void delegate ( char prefix, uint order, ulong order_val ) output_dg )
206 {
207     auto length = MetricPrefix.BinaryPrefixes.length;
208     verify (length < int.max);
209     for ( int order = cast(int) length - 1;  order >= 0; order-- )
210     {
211         auto shift = order * 10;
212 
213         ulong mask = 0x3ff;
214         mask <<= shift;
215 
216         auto order_val = (n & mask) >> shift;
217 
218         output_dg(MetricPrefix.BinaryPrefixes[order], order, order_val);
219     }
220 }