EngineeringFormatter.java
package org.djunits.value.formatter;
/**
* Format a value in Engineering notation, or normal floating point notation if that can represent the value more accurately.
* <p>
* Copyright (c) 2015-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
* BSD-style license. See <a href="https://djunits.org/docs/license.html">DJUNITS License</a>.
* </p>
* @version $Revision: 954 $, $LastChangedDate: 2022-01-10 03:42:57 +0100 (Mon, 10 Jan 2022) $, by $Author: averbraeck $,
* initial version 11 sep. 2015 <br>
* @author <a href="https://www.tudelft.nl/staff/p.knoppers/">Peter Knoppers</a>
*/
public final class EngineeringFormatter
{
/**
* This class shall never be instantiated.
*/
private EngineeringFormatter()
{
// Prevent instantiation of this class
}
/**
* Switch to/from upper case E for exponent indicator. The default is to use upper case.
* @param upper boolean; if true; an upper case E will be used; if false; a lower case e will be used
*/
public static void setUpperCaseFormat(final boolean upper)
{
exponentFormat = upper ? "%%%d.%dE" : "%%%d.%de";
}
/** Format constructor for mantissa plus exponent notation. */
private static String exponentFormat = "%%%d.%dE";
/** Format constructor for mantissa-only notation. */
private static final String FLOATFORMAT = "%%%d.%df";
/**
* Minimum value to display in non-scientific / non-engineering notation. The value 0.0001 ensures that we switch to
* mantissa + exponent notation around the point were we're about to lose precision due to underflow. This is the point
* where the width of the zeros before the first non-zero digit starts to exceed the width of a E + exponent value string.
*/
private static final double EFORMATLIMIT = 0.0001;
/**
* Format a double in Engineering format using <code>DEFAULTSIZE</code> room.
* @param val double; the value to format
* @return String; the formatted value
*/
public static String format(final double val)
{
return format(val, Format.DEFAULTSIZE);
}
/**
* Format a double in Engineering format.
* @param val double; the value to format
* @param room int; the width in characters of the result (minimum value is 10; values below this limit will be treated as
* 10)
* @return String; the formatted value
*/
public static String format(final double val, final int room)
{
if (room < 10)
{
return format(val, 10); // The EngineeringFormatter needs at least 10 positions
}
if (Double.isNaN(val) || 0d == val || Double.isInfinite(val))
{
String format = String.format(FLOATFORMAT, room, room);
return padd(String.format(format, val), room);
}
double absVal = Math.abs(val);
int roomForSignAndFraction = val > 0 ? 2 : 3;
// Floating point values should show at least one digit after the radix symbol (dot)
// max is the maximum value to display in non-scientific / non-engineering notation
double max = Math.pow(10, room - roomForSignAndFraction);
if (absVal < max - 0.5 && absVal > EFORMATLIMIT)
{
// Express as floating point number.
String format = String.format(FLOATFORMAT, room, room - 2);
String result = String.format(format, val);
int length = result.length();
if (length > room)
{
format = String.format(FLOATFORMAT, room, room - 2 + room - length);
result = String.format(format, val);
}
return result;
}
// Express in scientific notation using at least 2 digits for the exponent.
int roomForSignRadixAndExponent = val > 0 ? 6 : 7;
String format = String.format(exponentFormat, room, room - roomForSignRadixAndExponent);
String result = String.format(format, val);
int length = result.length();
if (length > room) // 3-digit exponent?
{
format = String.format(exponentFormat, room, room - length + room - roomForSignRadixAndExponent);
result = String.format(format, val);
}
result = convertToEngineering(result);
if (result.length() < room) // Exponent 100, or 101 was reduced to 99 which is one digit shorter
{
format = String.format(exponentFormat, room, 1 + room - length + room - roomForSignRadixAndExponent);
result = convertToEngineering(String.format(format, val));
}
return result;
}
/**
* Make the exponent of a floating point value a multiple of 3. Assumes that the first dot or comma is the radix symbol and
* 'e' or 'E' is used at the exponent symbol.
* @param in String; String representation of a floating point value
* @return String; The engineering formatted value
*/
public static String convertToEngineering(final String in)
{
int positionOfE = in.indexOf("E");
if (positionOfE < 0)
{
positionOfE = in.indexOf("e");
if (positionOfE < 0)
{
return in;
}
}
StringBuilder result = new StringBuilder();
int exponent = Integer.parseInt(in.substring(positionOfE + 1));
if (0 == exponent % 3)
{
return in;
}
String radix = null;
int pos;
for (pos = 0; pos < positionOfE; pos++)
{
String symbol = in.substring(pos, pos + 1);
Character c = symbol.charAt(0);
if (c != '.' && c != ',' || null != radix)
{
result.append(symbol);
}
else
{
radix = symbol;
break;
}
}
if (null == radix)
{
return in; // No radix symbol encountered
}
pos++;
// Shift the radix to the right
while (0 != exponent % 3)
{
String symbol = in.substring(pos, pos + 1);
result.append(symbol);
pos++;
exponent--;
}
result.append(radix);
for (; pos < positionOfE; pos++)
{
result.append(in.substring(pos, pos + 1));
}
result.append(in.substring(positionOfE, positionOfE + 1));
String exponentString = String.format("%+03d", exponent);
result.append(exponentString);
return result.toString();
}
/**
* Extend a String with spaces, or trim it to reach a specified length.
* @param in String; input string
* @param width int; length of the result
* @return String; the extended or trimmed input string
*/
public static String padd(final String in, final int width)
{
String format = String.format("%%%d.%ds", width, width);
return String.format(format, in);
}
}