26436478097049bd7f8f9928fce2408bac41fc90
[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         // This is the base of the fractions.  ie, 16d for 1/16ths.
10         private final int fractionBase;
11         // This is 1d/fractionBase;
12         private final double fractionValue;
13
14         // This is the value used when incrementing/decrementing.
15         private final double incrementValue;
16
17         // If the actual value differs from the decimal representation by more than this,
18         // we display as decimals.
19         private final double epsilon;
20
21         private final String unitLabel;
22
23         public FractionalUnit(double multiplier, String unit, String unitLabel, int fractionBase, double incrementValue) {
24                 this( multiplier, unit, unitLabel, fractionBase, incrementValue, 0.1d/fractionBase);
25         }
26
27         public FractionalUnit(double multiplier, String unit, String unitLabel, int fractionBase, double incrementValue, double epsilon) {
28                 super(multiplier, unit);
29                 this.unitLabel = unitLabel;
30                 this.fractionBase = fractionBase;
31                 this.fractionValue = 1.0d/fractionBase;
32                 this.incrementValue = incrementValue;
33                 this.epsilon = epsilon;
34         }
35
36         @Override
37         public double round(double value) {
38                 return roundTo( value, fractionValue );
39         }
40
41         private double roundTo( double value, double fraction ) {
42                 double remainder = Math.IEEEremainder( value, fraction );
43                 return value - remainder;
44         }
45
46         @Override
47         public double getNextValue(double value) {
48                 double rounded = roundTo(value, incrementValue);
49                 if ( rounded <= value + epsilon) {
50                         rounded += incrementValue;
51                 }
52                 return rounded;
53         }
54
55         @Override
56         public double getPreviousValue(double value) {
57                 double rounded = roundTo(value, incrementValue);
58                 if ( rounded >= value - epsilon ) {
59                         rounded -= incrementValue;
60                 }
61                 return rounded;
62         }
63
64         @Override
65         public Tick[] getTicks(double start, double end, double minor, double major) {
66                 // Convert values
67                 start = toUnit(start);
68                 end = toUnit(end);
69                 minor = toUnit(minor);
70                 major = toUnit(major);
71
72                 if (minor <= 0 || major <= 0 || major < minor) {
73                         throw new IllegalArgumentException("getTicks called with minor="+minor+" major="+major);
74                 }
75
76                 ArrayList<Tick> ticks = new ArrayList<Tick>();
77
78                 int mod2,mod3,mod4;  // Moduli for minor-notable, major-nonnotable, major-notable
79                 double minstep;
80
81                 // Find the smallest possible step size
82                 double one=1;
83                 while (one > minor)
84                         one /= 2;
85                 while (one < minor)
86                         one *= 2;
87                 minstep = one;
88                 mod2 = 16;
89
90                 // Find step size for major ticks
91                 one = 1;
92                 while (one > major)
93                         one /= 10;
94                 while (one < major)
95                         one *= 10;
96                 if (one/2 >= major) {
97                         // major step is round-five, major-notable is next round-ten
98                         double majorstep = one/2;
99                         mod3 = (int)Math.round(majorstep/minstep);
100                         mod4 = mod3*2;
101                 } else {
102                         // major step is round-ten, major-notable is next round-ten
103                         mod3 = (int)Math.round(one/minstep);
104                         mod4 = mod3*10;
105                 }
106                 // Check for clashes between minor-notable and major-nonnotable
107                 if (mod3 == mod2) {
108                         if (mod2==2)
109                                 mod2 = 1;  // Every minor tick is notable
110                         else
111                                 mod2 = 5;  // Every fifth minor tick is notable
112                 }
113
114
115                 // Calculate starting position
116                 int pos = (int)Math.ceil(start/minstep);
117                 //              System.out.println("mod2="+mod2+" mod3="+mod3+" mod4="+mod4);
118                 while (pos*minstep <= end) {
119                         double unitValue = pos*minstep;
120                         double value = fromUnit(unitValue);
121
122                         if (pos%mod4 == 0)
123                                 ticks.add(new Tick(value,unitValue,true,true));
124                         else if (pos%mod3 == 0)
125                                 ticks.add(new Tick(value,unitValue,true,false));
126                         else if (pos%mod2 == 0)
127                                 ticks.add(new Tick(value,unitValue,false,true));
128                         else
129                                 ticks.add(new Tick(value,unitValue,false,false));
130
131                         pos++;
132                 }
133
134                 return ticks.toArray(new Tick[0]);
135         }
136
137
138         @Override
139         public String toString(double value) {
140
141                 double correctVal = toUnit(value);
142                 double val = round(correctVal);
143
144
145                 if ( Math.abs( val - correctVal ) > epsilon ) {
146                         NumberFormat decFormat = new DecimalFormat("#.###");
147                         return decFormat.format(correctVal);
148                 }
149
150                 NumberFormat intFormat = new DecimalFormat("#");
151                 double sign = Math.signum(val);
152
153                 double posValue = sign * val;
154
155                 double intPart = Math.floor(posValue);
156
157                 double frac = Math.rint((posValue - intPart)/fractionValue);
158                 double fracBase = fractionBase;
159
160                 // Reduce fraction.
161                 while ( frac > 0 && fracBase > 2 && frac % 2 == 0 ) {
162                         frac /= 2.0;
163                         fracBase /= 2.0;
164                 }
165
166                 posValue *= sign;
167
168                 if ( frac == 0.0 )  {
169                         return intFormat.format(posValue);
170                 } else if (intPart == 0.0 ){
171                         return intFormat.format(sign*frac) + "/" + intFormat.format(fracBase);
172                 } else {
173                         return intFormat.format(sign*intPart) + " " + intFormat.format(frac) + "/" + intFormat.format(fracBase);
174                 }
175
176         }
177
178         @Override
179         public String toStringUnit(double value) {
180                 if (Double.isNaN(value))
181                         return "N/A";
182
183                 String s = toString(value);
184                 s += " " + unitLabel;
185                 return s;
186         }
187
188 }