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