740e4776d0b5ba73489c10335337bc204088b85a
[debian/openrocket] / core / src / net / sf / openrocket / unit / FractionalUnit.java
1 package net.sf.openrocket.unit;
2
3 import java.text.DecimalFormat;
4 import java.text.NumberFormat;
5 import java.util.ArrayList;
6
7 import net.sf.openrocket.util.Chars;
8
9 public class FractionalUnit extends Unit {
10         
11         private final static char FRACTION = Chars.FRACTION;
12         
13         private final static String[] NUMERATOR = {
14                         "\u2070", // 0
15                         "\u00B9", // 1
16                         "\u00B2", // 2
17                         "\u00B3", // 3
18                         "\u2074", // 4
19                         "\u2075", // 5
20                         "\u2076", // 6
21                         "\u2077", // 7
22                         "\u2078", // 8
23                         "\u2079" // 9
24         };
25         
26         private final static String[] DENOMINATOR = {
27                         "\u2080", // 0
28                         "\u2081", // 1
29                         "\u2082", // 2
30                         "\u2083", // 3
31                         "\u2084", // 4
32                         "\u2085", // 5
33                         "\u2086", // 6
34                         "\u2087", // 7
35                         "\u2088", // 8
36                         "\u2089" // 9
37         };
38         
39         // This is the base of the fractions.  ie, 16d for 1/16ths.
40         private final int fractionBase;
41         // This is 1d/fractionBase;
42         private final double fractionValue;
43         
44         // This is the value used when incrementing/decrementing.
45         private final double incrementValue;
46         
47         // If the actual value differs from the decimal representation by more than this,
48         // we display as decimals.
49         private final double epsilon;
50         
51         private final String unitLabel;
52         
53         public FractionalUnit(double multiplier, String unit, String unitLabel, int fractionBase, double incrementValue) {
54                 this(multiplier, unit, unitLabel, fractionBase, incrementValue, 0.1d / fractionBase);
55         }
56         
57         public FractionalUnit(double multiplier, String unit, String unitLabel, int fractionBase, double incrementValue, double epsilon) {
58                 super(multiplier, unit);
59                 this.unitLabel = unitLabel;
60                 this.fractionBase = fractionBase;
61                 this.fractionValue = 1.0d / fractionBase;
62                 this.incrementValue = incrementValue;
63                 this.epsilon = epsilon;
64         }
65         
66         @Override
67         public double round(double value) {
68                 return roundTo(value, fractionValue);
69         }
70         
71         private double roundTo(double value, double fraction) {
72                 double remainder = Math.IEEEremainder(value, fraction);
73                 return value - remainder;
74         }
75         
76         @Override
77         public double getNextValue(double value) {
78                 double rounded = roundTo(value, incrementValue);
79                 if (rounded <= value + epsilon) {
80                         rounded += incrementValue;
81                 }
82                 return rounded;
83         }
84         
85         @Override
86         public double getPreviousValue(double value) {
87                 double rounded = roundTo(value, incrementValue);
88                 if (rounded >= value - epsilon) {
89                         rounded -= incrementValue;
90                 }
91                 return rounded;
92         }
93         
94         @Override
95         public Tick[] getTicks(double start, double end, double minor, double major) {
96                 // Convert values
97                 start = toUnit(start);
98                 end = toUnit(end);
99                 minor = toUnit(minor);
100                 major = toUnit(major);
101                 
102                 if (minor <= 0 || major <= 0 || major < minor) {
103                         throw new IllegalArgumentException("getTicks called with minor=" + minor + " major=" + major);
104                 }
105                 
106                 ArrayList<Tick> ticks = new ArrayList<Tick>();
107                 
108                 int mod2, mod3, mod4; // Moduli for minor-notable, major-nonnotable, major-notable
109                 double minstep;
110                 
111                 // Find the smallest possible step size
112                 double one = 1;
113                 while (one > minor)
114                         one /= 2;
115                 while (one < minor)
116                         one *= 2;
117                 minstep = one;
118                 mod2 = 16;
119                 
120                 // Find step size for major ticks
121                 one = 1;
122                 while (one > major)
123                         one /= 10;
124                 while (one < major)
125                         one *= 10;
126                 if (one / 2 >= major) {
127                         // major step is round-five, major-notable is next round-ten
128                         double majorstep = one / 2;
129                         mod3 = (int) Math.round(majorstep / minstep);
130                         mod4 = mod3 * 2;
131                 } else {
132                         // major step is round-ten, major-notable is next round-ten
133                         mod3 = (int) Math.round(one / minstep);
134                         mod4 = mod3 * 10;
135                 }
136                 // Check for clashes between minor-notable and major-nonnotable
137                 if (mod3 == mod2) {
138                         if (mod2 == 2)
139                                 mod2 = 1; // Every minor tick is notable
140                         else
141                                 mod2 = 5; // Every fifth minor tick is notable
142                 }
143                 
144                 
145                 // Calculate starting position
146                 int pos = (int) Math.ceil(start / minstep);
147                 //              System.out.println("mod2="+mod2+" mod3="+mod3+" mod4="+mod4);
148                 while (pos * minstep <= end) {
149                         double unitValue = pos * minstep;
150                         double value = fromUnit(unitValue);
151                         
152                         if (pos % mod4 == 0)
153                                 ticks.add(new Tick(value, unitValue, true, true));
154                         else if (pos % mod3 == 0)
155                                 ticks.add(new Tick(value, unitValue, true, false));
156                         else if (pos % mod2 == 0)
157                                 ticks.add(new Tick(value, unitValue, false, true));
158                         else
159                                 ticks.add(new Tick(value, unitValue, false, false));
160                         
161                         pos++;
162                 }
163                 
164                 return ticks.toArray(new Tick[0]);
165         }
166         
167         
168         @Override
169         public String toString(double value) {
170                 
171                 double correctVal = toUnit(value);
172                 double val = round(correctVal);
173                 
174                 
175                 if (Math.abs(val - correctVal) > epsilon) {
176                         NumberFormat decFormat = new DecimalFormat("#.###");
177                         return decFormat.format(correctVal);
178                 }
179                 
180                 NumberFormat intFormat = new DecimalFormat("#");
181                 double sign = Math.signum(val);
182                 
183                 double posValue = sign * val;
184                 
185                 double intPart = Math.floor(posValue);
186                 
187                 double frac = Math.rint((posValue - intPart) / fractionValue);
188                 double fracBase = fractionBase;
189                 
190                 // Reduce fraction.
191                 while (frac > 0 && fracBase > 2 && frac % 2 == 0) {
192                         frac /= 2.0;
193                         fracBase /= 2.0;
194                 }
195                 
196                 posValue *= sign;
197                 
198                 if (frac == 0.0) {
199                         return intFormat.format(posValue);
200                 } else if (intPart == 0.0) {
201                         return (sign < 0 ? "-" : "") + numeratorString(Double.valueOf(frac).intValue())
202                                         + FRACTION + denominatorString(Double.valueOf(fracBase).intValue());
203                 } else {
204                         return intFormat.format(sign * intPart) + " " + numeratorString(Double.valueOf(frac).intValue())
205                                         + FRACTION + denominatorString(Double.valueOf(fracBase).intValue());
206                 }
207                 
208         }
209         
210         private String numeratorString(int value) {
211                 
212                 String rep = "";
213                 if (value == 0) {
214                         return "0";
215                 }
216                 while (value > 0) {
217                         rep = NUMERATOR[value % 10] + rep;
218                         value = value / 10;
219                 }
220                 return rep;
221         }
222         
223         private String denominatorString(int value) {
224                 String rep = "";
225                 if (value == 0) {
226                         return "0";
227                 }
228                 while (value > 0) {
229                         rep = DENOMINATOR[value % 10] + rep;
230                         value = value / 10;
231                 }
232                 return rep;
233         }
234         
235         @Override
236         public String toStringUnit(double value) {
237                 if (Double.isNaN(value))
238                         return "N/A";
239                 
240                 String s = toString(value);
241                 s += " " + unitLabel;
242                 return s;
243         }
244         
245 }