Human Number Formatting Example

By: a guest on Feb 13th, 2013  |  syntax: C#  |  size: 12.21 KB  |  views: 18  |  expires: Never
Text below is selected. Please press Ctrl+C to copy to your clipboard. (⌘+C on Mac)
1. using System;
2. using System.Collections.Generic;
3. using System.Linq;
4. using System.Text;
5.
6. namespace numtst
7. {
8.     class Program
9.     {
10.         static void Main(string[] args)
11.         {
12.             Console.WriteLine(Format(3.343e100, 0.05)); // 33 followed by 99 zeroes
13.             Console.WriteLine(Format(12.3456, 0.05)); // 12
14.             Console.WriteLine(Format(12.3456, 0.02)); // 12 and 1/3rd
15.             Console.WriteLine(Format(12.3456, 0.0005)); // 12.35
16.             Console.WriteLine(Format(12.3456, 0.0001)); // 12.346
17.             Console.WriteLine(Format(3.343e10, 0.05)); // 33 billion
18.             Console.WriteLine(Format(3.343e10, 0.01)); // 33 and 3/7ths billion
19.             Console.WriteLine(Format(3.343e15, 0.01)); // 3 and 1/3rd million billion
20.             Console.WriteLine(Format(3.343e15, 0.001)); // 3.34 million billion
21.             Console.WriteLine(Format(3.343e15, 0.0001)); // 3.343 million billion
23.         }
24.
25.         // This is the heart of our algorithm.
26.         static string Format(double num, double prefMaxError)
27.         {
28.             double absNum = Math.Abs(num);
29.             if (absNum < 0.00001) return "0";
30.
31.
32.             // We try a lot of things.
33.             List<FormatResult> list = new List<FormatResult>();
34.             FixedPoint0(absNum, list, 100);
35.             Fraction(absNum, list, 200);
36.             FixedPoint0Postfix(absNum, list, 230);
37.             FractionPostfix(absNum, list, 240);
38.             FixedPoint1Postfix(absNum, list, 250);
39.             FixedPoint2Postfix(absNum, list, 260);
40.             FixedPoint3Postfix(absNum, list, 270);
41.             FixedPoint1(absNum, list, 300);
42.             FixedPoint2(absNum, list, 400);
43.             FixedPoint3(absNum, list, 500);
44.             FollowedByZeroes(absNum, list, 0, 550);
45.             FollowedByZeroes(absNum, list, 1, 600);
46.             FollowedByZeroes(absNum, list, 2, 700);
47.             FollowedByZeroes(absNum, list, 3, 800);
48.             FollowedByZeroes(absNum, list, 4, 800);
49.             // Off-by-one not yet implimented...
50.             // Testing for nice postfix fractions like "1/3rd million" not implemented...
51.             // Billionths and billionths of billionths not implemented.
52.
53.             // If we couldn't find any acceptable representation give up.
54.             if (list.Count == 0)
55.                 return "Non-representable number.";
56.
57.             list.Sort(delegate(FormatResult a, FormatResult b)
58.             {
59.                 return a.Priority.CompareTo(b.Priority);
60.             });
61.
62.             // We select the best priority item that meets our relative error.
63.             foreach (FormatResult fr in list)
64.             {
65.                 double relError = Math.Abs((fr.ActualValue - absNum) / fr.ActualValue);
66.                 if (relError < prefMaxError)
67.                     return Finalize(num, fr);
68.             }
69.
70.             // Nothing found still... Return the most accurate instead.
71.
72.             list.Sort(delegate(FormatResult a, FormatResult b)
73.             {
74.                 double relErrorA = Math.Abs((a.ActualValue - absNum) / a.ActualValue);
75.                 double relErrorB = Math.Abs((b.ActualValue - absNum) / b.ActualValue);
76.                 if (relErrorA == relErrorB)
77.                     return a.Priority.CompareTo(b.Priority);
78.                 return relErrorA.CompareTo(relErrorB);
79.             });
80.
81.             return Finalize(num, list[0]);
82.         }
83.
84.         class FormatResult
85.         {
86.             public double ActualValue;
87.             public string Representation;
88.             public bool WrittenNegative;
89.             public int Priority;
90.             public FormatResult(double av, string rep, bool wn, int pr)
91.             {
92.                 ActualValue = av;
93.                 Representation = rep;
94.                 WrittenNegative = wn;
95.                 Priority = pr;
96.             }
97.         }
98.
99.         // Brute forces most accurate simple fraction
100.         static string FractionCore(double num, out double outActual, out bool negType)
101.         {
102.             outActual = 0;
103.             negType = false;
104.
105.             // We consider integer parts greater than this value
106.             // not needing of fractional parts.
107.             int maxThreshold = 1000;
108.             if (num >= maxThreshold) return null;
109.
110.             // Brute force
111.             double bestDiff = 10;
112.             int bestD = -1;
113.             int bestN = 0;
114.             for (int denom = 2; denom <= 10; denom++)
115.             {
116.                 int n = (int)Math.Round(num * denom);
117.                 double actual = n / (double)denom;
118.                 double diff = Math.Abs(actual - num);
119.                 if (diff < bestDiff)
120.                 {
121.                     bestDiff = diff;
122.                     bestD = denom;
123.                     bestN = n;
124.                 }
125.             }
126.
127.             if (bestD < 0) return null;
128.
129.             // Make a string out of the best result.
130.             int bestInt = bestN / bestD;
131.             int bestNum = bestN % bestD;
132.             int bestDen = bestD;
133.
134.             if (bestInt >= maxThreshold) return null;
135.
136.             string rep = "";
137.             if (bestInt > 0)
138.             {
139.                 rep += bestInt.ToString();
140.                 if (bestNum > 0)
141.                     rep += " and ";
142.             }
143.             if (bestNum > 0)
144.             {
145.                 rep += bestNum.ToString();
146.                 rep += "/";
147.                 rep += bestDen.ToString();
148.                 if (bestDen == 2) ;
149.                 else if (bestDen == 3) rep += "rd";
150.                 else rep += "th";
151.                 if (bestDen != 2 && bestNum > 1) rep += "s";
152.             }
153.
154.             if (bestInt == 0 && bestNum == 0) rep = "0";
155.
156.             double bestActual = (double)bestN / bestD;
157.
158.             outActual = bestActual;
159.             negType = bestInt > 0 && bestNum > 0;
160.
161.             return rep;
162.         }
163.
164.         static void Fraction(double num, List<FormatResult> list, int priority)
165.         {
166.             bool negType;
167.             double actual;
168.             string r = FractionCore(num, out actual, out negType);
169.             if (r == null) return;
170.             list.Add(new FormatResult(actual, r, negType, priority));
171.         }
172.
173.         static void FixedPoint0(double num, List<FormatResult> list, int priority)
174.         {
175.             int maxThreshold = 1000000;
176.             if (num > maxThreshold) num = maxThreshold;
177.             int actual = (int)Math.Round(num);
178.             string rep = string.Format("{0:#,0}", actual);
179.             list.Add(new FormatResult(actual, rep, false, priority));
180.         }
181.
182.         static void FixedPoint1(double num, List<FormatResult> list, int priority)
183.         {
184.             int maxThreshold = 1000000;
185.             if (num > maxThreshold) num = maxThreshold;
186.             int actual = (int)Math.Round(num, 1);
187.             string rep = string.Format("{0:#,0.0}", actual);
188.             list.Add(new FormatResult(actual, rep, false, priority));
189.         }
190.
191.         static void FixedPoint2(double num, List<FormatResult> list, int priority)
192.         {
193.             int maxThreshold = 1000000;
194.             if (num > maxThreshold) num = maxThreshold;
195.             int actual = (int)Math.Round(num, 2);
196.             string rep = string.Format("{0:#,0.00}", actual);
197.             list.Add(new FormatResult(actual, rep, false, priority));
198.         }
199.
200.         static void FixedPoint3(double num, List<FormatResult> list, int priority)
201.         {
202.             int maxThreshold = 1000000;
203.             if (num > maxThreshold) num = maxThreshold;
204.             int actual = (int)Math.Round(num, 3);
205.             string rep = string.Format("{0:#,0.000}", actual);
206.             list.Add(new FormatResult(actual, rep, false, priority));
207.         }
208.
209.         static string PreprocessPostfix(double num, out double start, out double outMult)
210.         {
211.             start = 0;
212.             outMult = 1;
213.
214.             int maxPostfixes = 3;
215.
216.             int numBillions = 0;
217.             bool million = false;
218.             bool thousand = false;
219.
220.             int numPostfixes = 0;
221.
222.             double mult = 1;
223.
224.             while (num > 1000000000 * mult)
225.             {
226.                 numPostfixes++;
227.                 numBillions++;
228.                 mult *= 1000000000;
229.             }
230.
231.             if (num > 1000000 * mult)
232.             {
233.                 numPostfixes++;
234.                 million = true;
235.                 mult *= 1000000;
236.             }
237.
238.             if (num > 1000 * mult)
239.             {
240.                 numPostfixes++;
241.                 thousand = true;
242.                 mult *= 1000;
243.             }
244.
245.             num /= mult;
246.
247.             if (numPostfixes > maxPostfixes) return null;
248.
249.             start = num;
250.             outMult = mult;
251.
252.             string res = "";
253.             if (thousand) res += " thousand";
254.             if (million) res += " million";
255.             for (int i = 0; i < numBillions; i++) res += " billion";
256.
257.             return res;
258.         }
259.
260.         static void FractionPostfix(double num, List<FormatResult> list, int priority)
261.         {
262.             double mult;
263.             string postfix = PreprocessPostfix(num, out num, out mult);
264.             if (postfix == null) return;
265.             double actual;
266.             bool negType;
267.             string rep = FractionCore(num, out actual, out negType) + postfix;
268.             if (rep == null) return;
269.             list.Add(new FormatResult(actual * mult, rep, true, priority));
270.         }
271.
272.         static void FixedPoint0Postfix(double num, List<FormatResult> list, int priority)
273.         {
274.             double mult;
275.             string postfix = PreprocessPostfix(num, out num, out mult);
276.             if (postfix == null) return;
277.             string rep = string.Format("{0:#,0}", num) + postfix;
278.             num = Math.Round(num);
279.             list.Add(new FormatResult(num * mult, rep, true, priority));
280.         }
281.
282.         static void FixedPoint1Postfix(double num, List<FormatResult> list, int priority)
283.         {
284.             double mult;
285.             string postfix = PreprocessPostfix(num, out num, out mult);
286.             if (postfix == null) return;
287.             string rep = string.Format("{0:#,0.0}", num) + postfix;
288.             num = Math.Round(num, 1);
289.             list.Add(new FormatResult(num * mult, rep, true, priority));
290.         }
291.
292.         static void FixedPoint2Postfix(double num, List<FormatResult> list, int priority)
293.         {
294.             double mult;
295.             string postfix = PreprocessPostfix(num, out num, out mult);
296.             if (postfix == null) return;
297.             string rep = string.Format("{0:#,0.00}", num) + postfix;
298.             num = Math.Round(num, 2);
299.             list.Add(new FormatResult(num * mult, rep, true, priority));
300.         }
301.
302.         static void FixedPoint3Postfix(double num, List<FormatResult> list, int priority)
303.         {
304.             double mult;
305.             string postfix = PreprocessPostfix(num, out num, out mult);
306.             if (postfix == null) return;
307.             string rep = string.Format("{0:#,0.000}", num) + postfix;
308.             num = Math.Round(num, 3);
309.             list.Add(new FormatResult(num * mult, rep, true, priority));
310.         }
311.
312.         static void FollowedByZeroes(double num, List<FormatResult> list, int numDigits, int priority)
313.         {
314.             if (num < 1) return;
315.             int numZeroes = (int)Math.Log10(num);
316.             if (numZeroes < numDigits) return;
317.             double mult = Math.Pow(10.0, numZeroes - numDigits);
318.             num /= mult;
319.             int digits = (int)Math.Round(num);
320.             string rep = string.Format("{0:#,0}", digits);
321.             int nz = numZeroes - numDigits;
322.             rep += " followed by " + nz.ToString();
323.             if (nz == 1)
324.                 rep += " zero";
325.             else
326.                 rep += " zeroes";
327.             double actual = digits * mult;
328.             list.Add(new FormatResult(actual, rep, false, priority));
329.         }
330.
331.         static string Finalize(double num, FormatResult fr)
332.         {
333.             if (num >= 0) return fr.Representation;
334.             if (fr.WrittenNegative)
335.                 return "negative " + fr.Representation;
336.             return "-" + fr.Representation;
337.         }
338.     }
339. }
clone this paste RAW Paste Data
Top