View Javadoc
1   /*
2   Copyright (c) 2018 James Ahlborn
3   
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7   
8       http://www.apache.org/licenses/LICENSE-2.0
9   
10  Unless required by applicable law or agreed to in writing, software
11  distributed under the License is distributed on an "AS IS" BASIS,
12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  See the License for the specific language governing permissions and
14  limitations under the License.
15  */
16  
17  package com.healthmarketscience.jackcess.impl.expr;
18  
19  import java.math.BigDecimal;
20  import java.math.MathContext;
21  import java.math.RoundingMode;
22  import java.text.DecimalFormat;
23  import java.text.DecimalFormatSymbols;
24  import java.text.FieldPosition;
25  import java.text.NumberFormat;
26  import java.text.ParsePosition;
27  
28  
29  /**
30   *
31   * @author James Ahlborn
32   */
33  public class NumberFormatter
34  {
35    public static final RoundingMode ROUND_MODE = RoundingMode.HALF_EVEN;
36  
37    /** designates the format of exponent notation used by ScientificFormat */
38    public enum NotationType {
39      /** Scientific notation "E", "E-" (default java behavior) */
40      EXP_E_MINUS {
41        @Override
42        protected void format(StringBuffer sb, int eIdx) {
43          // nothing to do
44        }
45      },
46      /** Scientific notation "E+", "E-" */
47      EXP_E_PLUS {
48        @Override
49        protected void format(StringBuffer sb, int eIdx) {
50          maybeInsertExpPlus(sb, eIdx);
51        }
52      },
53      /** Scientific notation "e", "e-" */
54      EXP_e_MINUS {
55        @Override
56        protected void format(StringBuffer sb, int eIdx) {
57          sb.setCharAt(eIdx, 'e');
58        }
59      },
60      /** Scientific notation "e+", "e-" */
61      EXP_e_PLUS {
62        @Override
63        protected void format(StringBuffer sb, int eIdx) {
64          sb.setCharAt(eIdx, 'e');
65          maybeInsertExpPlus(sb, eIdx);
66        }
67      };
68  
69      protected abstract void format(StringBuffer sb, int idx);
70    }
71  
72    private static final int FLT_SIG_DIGITS = 7;
73    private static final int DBL_SIG_DIGITS = 15;
74    private static final int DEC_SIG_DIGITS = 28;
75  
76    public static final MathContext FLT_MATH_CONTEXT =
77      new MathContext(FLT_SIG_DIGITS, ROUND_MODE);
78    public static final MathContext DBL_MATH_CONTEXT =
79      new MathContext(DBL_SIG_DIGITS, ROUND_MODE);
80    public static final MathContext DEC_MATH_CONTEXT =
81      new MathContext(DEC_SIG_DIGITS, ROUND_MODE);
82  
83    // note, java doesn't distinguish between pos/neg NaN
84    private static final String NAN_STR = "1.#QNAN";
85    private static final String POS_INF_STR = "1.#INF";
86    private static final String NEG_INf_STR = "-1.#INF";
87  
88    private final TypeFormatter _fltFmt;
89    private final TypeFormatter _dblFmt;
90    private final TypeFormatter _decFmt;
91  
92    public NumberFormatter(DecimalFormatSymbols syms) {
93      _fltFmt = new TypeFormatter(FLT_SIG_DIGITS, syms);
94      _dblFmt = new TypeFormatter(DBL_SIG_DIGITS, syms);
95      _decFmt = new TypeFormatter(DEC_SIG_DIGITS, syms);
96    }
97  
98    public String format(float f) {
99  
100     if(Float.isNaN(f)) {
101       return NAN_STR;
102     }
103     if(Float.isInfinite(f)) {
104       return ((f < 0f) ? NEG_INf_STR : POS_INF_STR);
105     }
106 
107     return _fltFmt.format(new BigDecimal(f, FLT_MATH_CONTEXT));
108   }
109 
110   public String format(double d) {
111 
112     if(Double.isNaN(d)) {
113       return NAN_STR;
114     }
115     if(Double.isInfinite(d)) {
116       return ((d < 0d) ? NEG_INf_STR : POS_INF_STR);
117     }
118 
119     return _dblFmt.format(new BigDecimal(d, DBL_MATH_CONTEXT));
120   }
121 
122   public String format(BigDecimal bd) {
123     return _decFmt.format(bd.round(DEC_MATH_CONTEXT));
124   }
125 
126   private static ScientificFormat createScientificFormat(
127       int prec, DecimalFormatSymbols syms) {
128     DecimalFormat df = new DecimalFormat("0.#E00", syms);
129     df.setMaximumIntegerDigits(1);
130     df.setMaximumFractionDigits(prec);
131     df.setRoundingMode(ROUND_MODE);
132     return new ScientificFormat(df);
133   }
134 
135   private static final class TypeFormatter
136   {
137     private final DecimalFormat _df;
138     private final ScientificFormat _dfS;
139     private final int _prec;
140 
141     private TypeFormatter(int prec, DecimalFormatSymbols syms) {
142       _prec = prec;
143       _df = new DecimalFormat("0.#", syms);
144       _df.setMaximumIntegerDigits(prec);
145       _df.setMaximumFractionDigits(prec);
146       _df.setRoundingMode(ROUND_MODE);
147       _dfS = createScientificFormat(prec, syms);
148     }
149 
150     public String format(BigDecimal bd) {
151       bd = bd.stripTrailingZeros();
152       int prec = bd.precision();
153       int scale = bd.scale();
154 
155       int sigDigits = prec;
156       if(scale < 0) {
157         sigDigits -= scale;
158       } else if(scale > prec) {
159         sigDigits += (scale - prec);
160       }
161 
162       return ((sigDigits > _prec) ? _dfS.format(bd) : _df.format(bd));
163     }
164   }
165 
166   private static void maybeInsertExpPlus(StringBuffer sb, int eIdx) {
167     if(sb.charAt(eIdx + 1) != '-') {
168       sb.insert(eIdx + 1, '+');
169     }
170   }
171 
172   public static class ScientificFormat extends NumberFormat
173   {
174     private static final long serialVersionUID = 0L;
175 
176     private final NumberFormat _df;
177     private final NotationType _type;
178 
179     public ScientificFormat(NumberFormat df) {
180       this(df, NotationType.EXP_E_PLUS);
181     }
182 
183     public ScientificFormat(NumberFormat df, NotationType type) {
184       _df = df;
185       _type = type;
186     }
187 
188     @Override
189     public StringBuffer format(Object number, StringBuffer toAppendTo,
190                                FieldPosition pos)
191     {
192       StringBuffer sb = _df.format(number, toAppendTo, pos);
193       _type.format(sb, sb.lastIndexOf("E"));
194       return sb;
195     }
196 
197     @Override
198     public StringBuffer format(double number, StringBuffer toAppendTo,
199                                FieldPosition pos) {
200       throw new UnsupportedOperationException();
201     }
202 
203     @Override
204     public Number parse(String source, ParsePosition parsePosition) {
205       throw new UnsupportedOperationException();
206     }
207 
208     @Override
209     public StringBuffer format(long number, StringBuffer toAppendTo,
210                                FieldPosition pos) {
211       throw new UnsupportedOperationException();
212     }
213 
214     @Override
215     public int getMaximumFractionDigits() {
216       return _df.getMaximumFractionDigits();
217     }
218 
219     @Override
220     public int getMinimumFractionDigits() {
221       return _df.getMinimumFractionDigits();
222     }
223   }
224 }