aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/com/healthmarketscience/jackcess/impl/expr/NumberFormatter.java
blob: 4f886ff520a4b3196fd7873e942d67a344149019 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
/*
Copyright (c) 2018 James Ahlborn

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package com.healthmarketscience.jackcess.impl.expr;

import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.FieldPosition;
import java.text.NumberFormat;
import java.text.ParsePosition;


/**
 *
 * @author James Ahlborn
 */
public class NumberFormatter
{
  public static final RoundingMode ROUND_MODE = RoundingMode.HALF_EVEN;

  /** designates the format of exponent notation used by ScientificFormat */
  public enum NotationType {
    /** Scientific notation "E", "E-" (default java behavior) */
    EXP_E_MINUS {
      @Override
      protected void format(StringBuffer sb, int eIdx) {
        // nothing to do
      }
    },
    /** Scientific notation "E+", "E-" */
    EXP_E_PLUS {
      @Override
      protected void format(StringBuffer sb, int eIdx) {
        maybeInsertExpPlus(sb, eIdx);
      }
    },
    /** Scientific notation "e", "e-" */
    EXP_e_MINUS {
      @Override
      protected void format(StringBuffer sb, int eIdx) {
        sb.setCharAt(eIdx, 'e');
      }
    },
    /** Scientific notation "e+", "e-" */
    EXP_e_PLUS {
      @Override
      protected void format(StringBuffer sb, int eIdx) {
        sb.setCharAt(eIdx, 'e');
        maybeInsertExpPlus(sb, eIdx);
      }
    };

    protected abstract void format(StringBuffer sb, int idx);
  }

  private static final int FLT_SIG_DIGITS = 7;
  private static final int DBL_SIG_DIGITS = 15;
  private static final int DEC_SIG_DIGITS = 28;

  public static final MathContext FLT_MATH_CONTEXT =
    new MathContext(FLT_SIG_DIGITS, ROUND_MODE);
  public static final MathContext DBL_MATH_CONTEXT =
    new MathContext(DBL_SIG_DIGITS, ROUND_MODE);
  public static final MathContext DEC_MATH_CONTEXT =
    new MathContext(DEC_SIG_DIGITS, ROUND_MODE);

  // note, java doesn't distinguish between pos/neg NaN
  private static final String NAN_STR = "1.#QNAN";
  private static final String POS_INF_STR = "1.#INF";
  private static final String NEG_INf_STR = "-1.#INF";

  private final TypeFormatter _fltFmt;
  private final TypeFormatter _dblFmt;
  private final TypeFormatter _decFmt;

  public NumberFormatter(DecimalFormatSymbols syms) {
    _fltFmt = new TypeFormatter(FLT_SIG_DIGITS, syms);
    _dblFmt = new TypeFormatter(DBL_SIG_DIGITS, syms);
    _decFmt = new TypeFormatter(DEC_SIG_DIGITS, syms);
  }

  public String format(float f) {

    if(Float.isNaN(f)) {
      return NAN_STR;
    }
    if(Float.isInfinite(f)) {
      return ((f < 0f) ? NEG_INf_STR : POS_INF_STR);
    }

    return _fltFmt.format(new BigDecimal(f, FLT_MATH_CONTEXT));
  }

  public String format(double d) {

    if(Double.isNaN(d)) {
      return NAN_STR;
    }
    if(Double.isInfinite(d)) {
      return ((d < 0d) ? NEG_INf_STR : POS_INF_STR);
    }

    return _dblFmt.format(new BigDecimal(d, DBL_MATH_CONTEXT));
  }

  public String format(BigDecimal bd) {
    return _decFmt.format(bd.round(DEC_MATH_CONTEXT));
  }

  private static ScientificFormat createScientificFormat(
      int prec, DecimalFormatSymbols syms) {
    DecimalFormat df = new DecimalFormat("0.#E00", syms);
    df.setMaximumIntegerDigits(1);
    df.setMaximumFractionDigits(prec);
    df.setRoundingMode(ROUND_MODE);
    return new ScientificFormat(df);
  }

  private static final class TypeFormatter
  {
    private final DecimalFormat _df;
    private final ScientificFormat _dfS;
    private final int _prec;

    private TypeFormatter(int prec, DecimalFormatSymbols syms) {
      _prec = prec;
      _df = new DecimalFormat("0.#", syms);
      _df.setMaximumIntegerDigits(prec);
      _df.setMaximumFractionDigits(prec);
      _df.setRoundingMode(ROUND_MODE);
      _dfS = createScientificFormat(prec, syms);
    }

    public String format(BigDecimal bd) {
      bd = bd.stripTrailingZeros();
      int prec = bd.precision();
      int scale = bd.scale();

      int sigDigits = prec;
      if(scale < 0) {
        sigDigits -= scale;
      } else if(scale > prec) {
        sigDigits += (scale - prec);
      }

      return ((sigDigits > _prec) ? _dfS.format(bd) : _df.format(bd));
    }
  }

  private static void maybeInsertExpPlus(StringBuffer sb, int eIdx) {
    if(sb.charAt(eIdx + 1) != '-') {
      sb.insert(eIdx + 1, '+');
    }
  }

  public static class ScientificFormat extends NumberFormat
  {
    private static final long serialVersionUID = 0L;

    private final NumberFormat _df;
    private final NotationType _type;

    public ScientificFormat(NumberFormat df) {
      this(df, NotationType.EXP_E_PLUS);
    }

    public ScientificFormat(NumberFormat df, NotationType type) {
      _df = df;
      _type = type;
    }

    @Override
    public StringBuffer format(Object number, StringBuffer toAppendTo,
                               FieldPosition pos)
    {
      StringBuffer sb = _df.format(number, toAppendTo, pos);
      _type.format(sb, sb.lastIndexOf("E"));
      return sb;
    }

    @Override
    public StringBuffer format(double number, StringBuffer toAppendTo,
                               FieldPosition pos) {
      throw new UnsupportedOperationException();
    }

    @Override
    public Number parse(String source, ParsePosition parsePosition) {
      throw new UnsupportedOperationException();
    }

    @Override
    public StringBuffer format(long number, StringBuffer toAppendTo,
                               FieldPosition pos) {
      throw new UnsupportedOperationException();
    }

    @Override
    public int getMaximumFractionDigits() {
      return _df.getMaximumFractionDigits();
    }

    @Override
    public int getMinimumFractionDigits() {
      return _df.getMinimumFractionDigits();
    }
  }
}