Je voudrais formater les nombres suivants dans les nombres à côté d'eux avec Java:
1000 to 1k
5821 to 5.8k
10500 to 10k
101800 to 101k
2000000 to 2m
7800000 to 7.8m
92150000 to 92m
123200000 to 123m
Le nombre à droite sera long ou entier, le nombre à gauche sera chaîne. Comment devrais-je aborder cela. J'ai déjà fait peu d'algorithme pour cela, mais je pensais qu'il y avait peut-être déjà quelque chose d'inventé qui fait du bon travail et ne nécessite pas de tests supplémentaires si je commence à traiter avec des milliards et des milliards de milliards :)
Exigences supplémentaires:
Voici une solution qui fonctionne pour toute valeur longue et que je trouve assez lisible (la logique de base est faite en bas trois lignes de la méthode format
).
Il utilise TreeMap
pour trouver le suffixe approprié. C'est étonnamment plus efficace qu'une solution précédente que j'écrivais qui utilisait des tableaux et était plus difficile à lire.
private static final NavigableMap<Long, String> suffixes = new TreeMap<> ();
static {
suffixes.put(1_000L, "k");
suffixes.put(1_000_000L, "M");
suffixes.put(1_000_000_000L, "G");
suffixes.put(1_000_000_000_000L, "T");
suffixes.put(1_000_000_000_000_000L, "P");
suffixes.put(1_000_000_000_000_000_000L, "E");
}
public static String format(long value) {
//Long.MIN_VALUE == -Long.MIN_VALUE so we need an adjustment here
if (value == Long.MIN_VALUE) return format(Long.MIN_VALUE + 1);
if (value < 0) return "-" + format(-value);
if (value < 1000) return Long.toString(value); //deal with easy case
Entry<Long, String> e = suffixes.floorEntry(value);
Long divideBy = e.getKey();
String suffix = e.getValue();
long truncated = value / (divideBy / 10); //the number part of the output times 10
boolean hasDecimal = truncated < 100 && (truncated / 10d) != (truncated / 10);
return hasDecimal ? (truncated / 10d) + suffix : (truncated / 10) + suffix;
}
public static void main(String args[]) {
long[] numbers = {0, 5, 999, 1_000, -5_821, 10_500, -101_800, 2_000_000, -7_800_000, 92_150_000, 123_200_000, 9_999_999, 999_999_999_999_999_999L, 1_230_000_000_000_000L, Long.MIN_VALUE, Long.MAX_VALUE};
String[] expected = {"0", "5", "999", "1k", "-5.8k", "10k", "-101k", "2M", "-7.8M", "92M", "123M", "9.9M", "999P", "1.2P", "-9.2E", "9.2E"};
for (int i = 0; i < numbers.length; i++) {
long n = numbers[i];
String formatted = format(n);
System.out.println(n + " => " + formatted);
if (!formatted.equals(expected[i])) throw new AssertionError("Expected: " + expected[i] + " but found: " + formatted);
}
}
Je sais, cela ressemble plus à un programme C, mais c'est super léger!
public static void main(String args[]) {
long[] numbers = new long[]{1000, 5821, 10500, 101800, 2000000, 7800000, 92150000, 123200000, 9999999};
for(long n : numbers) {
System.out.println(n + " => " + coolFormat(n, 0));
}
}
private static char[] c = new char[]{'k', 'm', 'b', 't'};
/**
* Recursive implementation, invokes itself for each factor of a thousand, increasing the class on each invokation.
* @param n the number to format
* @param iteration in fact this is the class from the array c
* @return a String representing the number n formatted in a cool looking way.
*/
private static String coolFormat(double n, int iteration) {
double d = ((long) n / 100) / 10.0;
boolean isRound = (d * 10) %10 == 0;//true if the decimal part is equal to 0 (then it's trimmed anyway)
return (d < 1000? //this determines the class, i.e. 'k', 'm' etc
((d > 99.9 || isRound || (!isRound && d > 9.99)? //this decides whether to trim the decimals
(int) d * 10 / 10 : d + "" // (int) d * 10 / 10 drops the decimal
) + "" + c[iteration])
: coolFormat(d, iteration+1));
}
Il produit:
1000 => 1k
5821 => 5.8k
10500 => 10k
101800 => 101k
2000000 => 2m
7800000 => 7.8m
92150000 => 92m
123200000 => 123m
9999999 => 9.9m
Voici une solution qui utilise la notation d'ingénierie de DecimalFormat:
public static void main(String args[]) {
long[] numbers = new long[]{7, 12, 856, 1000, 5821, 10500, 101800, 2000000, 7800000, 92150000, 123200000, 9999999};
for(long number : numbers) {
System.out.println(number + " = " + format(number));
}
}
private static String[] suffix = new String[]{"","k", "m", "b", "t"};
private static int MAX_LENGTH = 4;
private static String format(double number) {
String r = new DecimalFormat("##0E0").format(number);
r = r.replaceAll("E[0-9]", suffix[Character.getNumericValue(r.charAt(r.length() - 1)) / 3]);
while(r.length() > MAX_LENGTH || r.matches("[0-9]+\\.[a-z]")){
r = r.substring(0, r.length()-2) + r.substring(r.length() - 1);
}
return r;
}
Sortie:
7 = 7
12 = 12
856 = 856
1000 = 1k
5821 = 5.8k
10500 = 10k
101800 = 102k
2000000 = 2m
7800000 = 7.8m
92150000 = 92m
123200000 = 123m
9999999 = 10m
Besoin d'amélioration, mais: StrictMath à la rescousse!
Vous pouvez placer le suffixe dans une chaîne ou un tableau et les extraire en fonction de la puissance, ou de quelque chose du genre.
La division peut également être gérée autour du pouvoir, je pense que presque tout est sur la valeur du pouvoir. J'espère que ça aide!
public static String formatValue(double value) {
int power;
String suffix = " kmbt";
String formattedNumber = "";
NumberFormat formatter = new DecimalFormat("#,###.#");
power = (int)StrictMath.log10(value);
value = value/(Math.pow(10,(power/3)*3));
formattedNumber=formatter.format(value);
formattedNumber = formattedNumber + suffix.charAt(power/3);
return formattedNumber.length()>4 ? formattedNumber.replaceAll("\\.[0-9]+", "") : formattedNumber;
}
les sorties:
999
1,2k
98k
911k
1,1 m
11b
712b
34t
Cette solution (une extension de cette réponse ) résout les problèmes ci-dessus.
import org.Apache.commons.lang.math.NumberUtils;
import Java.text.DecimalFormat;
import Java.text.FieldPosition;
import Java.text.Format;
import Java.text.ParsePosition;
import Java.util.regex.Pattern;
/**
* Converts a number to a string in <a href="http://en.wikipedia.org/wiki/Metric_prefix">metric prefix</a> format.
* For example, 7800000 will be formatted as '7.8M'. Numbers under 1000 will be unchanged. Refer to the tests for further examples.
*/
class RoundedMetricPrefixFormat extends Format {
private static final String[] METRIC_PREFIXES = new String[]{"", "k", "M", "G", "T"};
/**
* The maximum number of characters in the output, excluding the negative sign
*/
private static final Integer MAX_LENGTH = 4;
private static final Pattern TRAILING_DECIMAL_POINT = Pattern.compile("[0-9]+\\.[kMGT]");
private static final Pattern METRIC_PREFIXED_NUMBER = Pattern.compile("\\-?[0-9]+(\\.[0-9])?[kMGT]");
@Override
public StringBuffer format(Object obj, StringBuffer output, FieldPosition pos) {
Double number = Double.valueOf(obj.toString());
// if the number is negative, convert it to a positive number and add the minus sign to the output at the end
boolean isNegative = number < 0;
number = Math.abs(number);
String result = new DecimalFormat("##0E0").format(number);
Integer index = Character.getNumericValue(result.charAt(result.length() - 1)) / 3;
result = result.replaceAll("E[0-9]", METRIC_PREFIXES[index]);
while (result.length() > MAX_LENGTH || TRAILING_DECIMAL_POINT.matcher(result).matches()) {
int length = result.length();
result = result.substring(0, length - 2) + result.substring(length - 1);
}
return output.append(isNegative ? "-" + result : result);
}
/**
* Convert a String produced by <tt>format()</tt> back to a number. This will generally not restore
* the original number because <tt>format()</tt> is a lossy operation, e.g.
*
* <pre>
* {@code
* def formatter = new RoundedMetricPrefixFormat()
* Long number = 5821L
* String formattedNumber = formatter.format(number)
* assert formattedNumber == '5.8k'
*
* Long parsedNumber = formatter.parseObject(formattedNumber)
* assert parsedNumber == 5800
* assert parsedNumber != number
* }
* </pre>
*
* @param source a number that may have a metric prefix
* @param pos if parsing succeeds, this should be updated to the index after the last parsed character
* @return a Number if the the string is a number without a metric prefix, or a Long if it has a metric prefix
*/
@Override
public Object parseObject(String source, ParsePosition pos) {
if (NumberUtils.isNumber(source)) {
// if the value is a number (without a prefix) don't return it as a Long or we'll lose any decimals
pos.setIndex(source.length());
return toNumber(source);
} else if (METRIC_PREFIXED_NUMBER.matcher(source).matches()) {
boolean isNegative = source.charAt(0) == '-';
int length = source.length();
String number = isNegative ? source.substring(1, length - 1) : source.substring(0, length - 1);
String metricPrefix = Character.toString(source.charAt(length - 1));
Number absoluteNumber = toNumber(number);
int index = 0;
for (; index < METRIC_PREFIXES.length; index++) {
if (METRIC_PREFIXES[index].equals(metricPrefix)) {
break;
}
}
Integer exponent = 3 * index;
Double factor = Math.pow(10, exponent);
factor *= isNegative ? -1 : 1;
pos.setIndex(source.length());
Float result = absoluteNumber.floatValue() * factor.longValue();
return result.longValue();
}
return null;
}
private static Number toNumber(String number) {
return NumberUtils.createNumber(number);
}
}
La solution a été écrite à l'origine dans Groovy comme indiqué ci-dessous.
import org.Apache.commons.lang.math.NumberUtils
import Java.text.DecimalFormat
import Java.text.FieldPosition
import Java.text.Format
import Java.text.ParsePosition
import Java.util.regex.Pattern
/**
* Converts a number to a string in <a href="http://en.wikipedia.org/wiki/Metric_prefix">metric prefix</a> format.
* For example, 7800000 will be formatted as '7.8M'. Numbers under 1000 will be unchanged. Refer to the tests for further examples.
*/
class RoundedMetricPrefixFormat extends Format {
private static final METRIC_PREFIXES = ["", "k", "M", "G", "T"]
/**
* The maximum number of characters in the output, excluding the negative sign
*/
private static final Integer MAX_LENGTH = 4
private static final Pattern TRAILING_DECIMAL_POINT = ~/[0-9]+\.[kMGT]/
private static final Pattern METRIC_PREFIXED_NUMBER = ~/\-?[0-9]+(\.[0-9])?[kMGT]/
@Override
StringBuffer format(Object obj, StringBuffer output, FieldPosition pos) {
Double number = obj as Double
// if the number is negative, convert it to a positive number and add the minus sign to the output at the end
boolean isNegative = number < 0
number = Math.abs(number)
String result = new DecimalFormat("##0E0").format(number)
Integer index = Character.getNumericValue(result.charAt(result.size() - 1)) / 3
result = result.replaceAll("E[0-9]", METRIC_PREFIXES[index])
while (result.size() > MAX_LENGTH || TRAILING_DECIMAL_POINT.matcher(result).matches()) {
int length = result.size()
result = result.substring(0, length - 2) + result.substring(length - 1)
}
output << (isNegative ? "-$result" : result)
}
/**
* Convert a String produced by <tt>format()</tt> back to a number. This will generally not restore
* the original number because <tt>format()</tt> is a lossy operation, e.g.
*
* <pre>
* {@code
* def formatter = new RoundedMetricPrefixFormat()
* Long number = 5821L
* String formattedNumber = formatter.format(number)
* assert formattedNumber == '5.8k'
*
* Long parsedNumber = formatter.parseObject(formattedNumber)
* assert parsedNumber == 5800
* assert parsedNumber != number
* }
* </pre>
*
* @param source a number that may have a metric prefix
* @param pos if parsing succeeds, this should be updated to the index after the last parsed character
* @return a Number if the the string is a number without a metric prefix, or a Long if it has a metric prefix
*/
@Override
Object parseObject(String source, ParsePosition pos) {
if (source.isNumber()) {
// if the value is a number (without a prefix) don't return it as a Long or we'll lose any decimals
pos.index = source.size()
toNumber(source)
} else if (METRIC_PREFIXED_NUMBER.matcher(source).matches()) {
boolean isNegative = source[0] == '-'
String number = isNegative ? source[1..-2] : source[0..-2]
String metricPrefix = source[-1]
Number absoluteNumber = toNumber(number)
Integer exponent = 3 * METRIC_PREFIXES.indexOf(metricPrefix)
Long factor = 10 ** exponent
factor *= isNegative ? -1 : 1
pos.index = source.size()
(absoluteNumber * factor) as Long
}
}
private static Number toNumber(String number) {
NumberUtils.createNumber(number)
}
}
Les tests sont écrits en Groovy mais peuvent être utilisés pour vérifier soit la classe Java, soit la classe Groovy (car elles portent le même nom et la même API)).
import Java.text.Format
import Java.text.ParseException
class RoundedMetricPrefixFormatTests extends GroovyTestCase {
private Format roundedMetricPrefixFormat = new RoundedMetricPrefixFormat()
void testNumberFormatting() {
[
7L : '7',
12L : '12',
856L : '856',
1000L : '1k',
(-1000L) : '-1k',
5821L : '5.8k',
10500L : '10k',
101800L : '102k',
2000000L : '2M',
7800000L : '7.8M',
(-7800000L): '-7.8M',
92150000L : '92M',
123200000L : '123M',
9999999L : '10M',
(-9999999L): '-10M'
].each { Long rawValue, String expectedRoundValue ->
assertEquals expectedRoundValue, roundedMetricPrefixFormat.format(rawValue)
}
}
void testStringParsingSuccess() {
[
'7' : 7,
'8.2' : 8.2F,
'856' : 856,
'-856' : -856,
'1k' : 1000,
'5.8k' : 5800,
'-5.8k': -5800,
'10k' : 10000,
'102k' : 102000,
'2M' : 2000000,
'7.8M' : 7800000L,
'92M' : 92000000L,
'-92M' : -92000000L,
'123M' : 123000000L,
'10M' : 10000000L
].each { String metricPrefixNumber, Number expectedValue ->
def parsedNumber = roundedMetricPrefixFormat.parseObject(metricPrefixNumber)
assertEquals expectedValue, parsedNumber
}
}
void testStringParsingFail() {
shouldFail(ParseException) {
roundedMetricPrefixFormat.parseObject('notNumber')
}
}
}
Le ICU lib a un formateur basé sur des règles pour les nombres, qui peut être utilisé pour épeler des nombres, etc. Je pense qu'en utilisant ICU vous donnerait une solution lisible et maintenable.
[Utilisation]
La bonne classe est RuleBasedNumberFormat. Le format lui-même peut être stocké sous forme de fichier séparé (ou sous forme de constante de chaîne, IIRC).
Exemple de http://userguide.icu-project.org/formatparse/numbers
double num = 2718.28;
NumberFormat formatter =
new RuleBasedNumberFormat(RuleBasedNumberFormat.SPELLOUT);
String result = formatter.format(num);
System.out.println(result);
La même page affiche des chiffres romains, alors je suppose que votre cas devrait également être possible.
Important: Les réponses transtypées vers double
échoueront pour des nombres tels que 99999999999999999L
Et renverront 100P
Au lieu de 99P
Car double
utilise le IEEE
standard :
Si une chaîne décimale avec 15 chiffres significatifs au plus est convertie en une représentation à double précision IEEE 754, puis reconvertie en une chaîne comportant le même nombre de chiffres significatifs, la chaîne finale doit alors correspondre à l'original. . [
long
a jusqu'à 19 chiffres significatifs.]
System.out.println((long)(double)99999999999999992L); // 100000000000000000
System.out.println((long)(double)99999999999999991L); // 99999999999999984
// it is even worse for the logarithm:
System.out.println(Math.log10(99999999999999600L)); // 17.0
System.out.println(Math.log10(99999999999999500L)); // 16.999999999999996
Cette solution élimine les chiffres indésirables et fonctionne pour toutes les valeurs long
. Mise en œuvre simple mais performante (comparaison ci-dessous). -120k ne peut pas être exprimé avec 4 caractères, même -0.1M est trop long, c'est pourquoi pour les nombres négatifs, 5 caractères doivent être corrects:
private static final char[] magnitudes = {'k', 'M', 'G', 'T', 'P', 'E'}; // enough for long
public static final String convert(long number) {
String ret;
if (number >= 0) {
ret = "";
} else if (number <= -9200000000000000000L) {
return "-9.2E";
} else {
ret = "-";
number = -number;
}
if (number < 1000)
return ret + number;
for (int i = 0; ; i++) {
if (number < 10000 && number % 1000 >= 100)
return ret + (number / 1000) + '.' + ((number % 1000) / 100) + magnitudes[i];
number /= 1000;
if (number < 1000)
return ret + number + magnitudes[i];
}
}
Le test de else if
Au début est nécessaire car min est -(2^63)
et max est (2^63)-1
. Par conséquent, l'affectation number = -number
Échouera si number == Long.MIN_VALUE
. Si nous devons faire une vérification, nous pouvons également inclure autant de nombres que possible au lieu de simplement vérifier number == Long.MIN_VALUE
.
La comparaison de cette implémentation avec celui qui a obtenu le plus de votes (dit le plus rapide actuellement) a montré qu’elle est plus de 5 fois plus rapide (il dépend des paramètres de test, mais avec plus de chiffres, le gain augmente et cette implémentation doit effectuer plus de contrôles, car elle gère tous les cas; ainsi, si l’autre était réglé, la différence deviendrait encore plus grande). C'est aussi rapide qu'il n'y a pas d'opération à virgule flottante, pas de logarithme, pas de puissance, pas de récursion, pas de regex, pas de formateur sophistiqué et de minimisation du nombre d'objets créés.
Voici le programme de test:
public class Test {
public static void main(String[] args) {
long[] numbers = new long[20000000];
for (int i = 0; i < numbers.length; i++)
numbers[i] = Math.random() < 0.5 ? (long) (Math.random() * Long.MAX_VALUE) : (long) (Math.random() * Long.MIN_VALUE);
System.out.println(convert1(numbers) + " vs. " + convert2(numbers));
}
private static long convert1(long[] numbers) {
long l = System.currentTimeMillis();
for (int i = 0; i < numbers.length; i++)
Converter1.convert(numbers[i]);
return System.currentTimeMillis() - l;
}
private static long convert2(long[] numbers) {
long l = System.currentTimeMillis();
for (int i = 0; i < numbers.length; i++)
Converter2.coolFormat(numbers[i], 0);
return System.currentTimeMillis() - l;
}
}
Sortie possible: 2309 vs. 11591
(À peu près la même chose lorsque vous n'utilisez que des nombres positifs et beaucoup plus extrême lorsque vous inversez l'ordre d'exécution, cela a peut-être quelque chose à voir avec le garbage collection)
Voici une courte mise en œuvre sans récursivité et juste une très petite boucle. Ne fonctionne pas avec les nombres négatifs mais supporte tous les positifs long
s jusqu'à Long.MAX_VALUE
:
private static final char[] SUFFIXES = {'k', 'm', 'g', 't', 'p', 'e' };
public static String format(long number) {
if(number < 1000) {
// No need to format this
return String.valueOf(number);
}
// Convert to a string
final String string = String.valueOf(number);
// The suffix we're using, 1-based
final int magnitude = (string.length() - 1) / 3;
// The number of digits we must show before the prefix
final int digits = (string.length() - 1) % 3 + 1;
// Build the string
char[] value = new char[4];
for(int i = 0; i < digits; i++) {
value[i] = string.charAt(i);
}
int valueLength = digits;
// Can and should we add a decimal point and an additional number?
if(digits == 1 && string.charAt(1) != '0') {
value[valueLength++] = '.';
value[valueLength++] = string.charAt(1);
}
value[valueLength++] = SUFFIXES[magnitude - 1];
return new String(value, 0, valueLength);
}
Les sorties:
1k
5,8k
10k
101k
2m
7.8m
92m
123m
9.2e (c'estLong.MAX_VALUE
)
J'ai également effectué une analyse comparative très simple (formatant 10 millions de disques aléatoires) et elle est considérablement plus rapide que l'implémentation d'Elijah et légèrement plus rapide que celle d'Assylias.
Mine: 1137,028 ms
Élie: 2664,396 m
'Assylias': 1373,473 ms
Avec JDK/12 et les versions ultérieures, vous pouvez maintenant utiliser NumberFormat.getCompactNumberInstance
pour formater les nombres. Vous pouvez créer un NumberFormat
d'abord en tant que
NumberFormat fmt = NumberFormat.getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT);
puis utilisez-le pour format
:
fmt.format(1000) $5 ==> "1K" fmt.format(10000000) $9 ==> "10M" fmt.format(1000000000) $11 ==> "1B"
Le code suivant montre comment faire cela avec une expansion facile à l’esprit.
La "magie" réside principalement dans la fonction makeDecimal
qui, pour les valeurs correctes transmises, garantit que vous ne disposerez jamais de plus de quatre caractères.
Il extrait d’abord les parties entières et les dixièmes d’un diviseur donné. Ainsi, par exemple, 12,345,678
Avec un diviseur de 1,000,000
Donnera une whole
valeur de 12
et une valeur tenths
de 3
.
À partir de là, il peut décider s'il génère uniquement la partie entière ou à la fois la partie entière et les dixièmes, en utilisant les règles suivantes:
Le code pour cela suit:
static private String makeDecimal(long val, long div, String sfx) {
val = val / (div / 10);
long whole = val / 10;
long tenths = val % 10;
if ((tenths == 0) || (whole >= 10))
return String.format("%d%s", whole, sfx);
return String.format("%d.%d%s", whole, tenths, sfx);
}
Ensuite, il suffit d'appeler cette fonction d'assistance avec les valeurs correctes, y compris des constantes pour simplifier la vie du développeur:
static final long THOU = 1000L;
static final long MILL = 1000000L;
static final long BILL = 1000000000L;
static final long TRIL = 1000000000000L;
static final long QUAD = 1000000000000000L;
static final long QUIN = 1000000000000000000L;
static private String Xlat(long val) {
if (val < THOU) return Long.toString(val);
if (val < MILL) return makeDecimal(val, THOU, "k");
if (val < BILL) return makeDecimal(val, MILL, "m");
if (val < TRIL) return makeDecimal(val, BILL, "b");
if (val < QUAD) return makeDecimal(val, TRIL, "t");
if (val < QUIN) return makeDecimal(val, QUAD, "q");
return makeDecimal(val, QUIN, "u");
}
Le fait que la fonction makeDecimal
fasse le gros du travail signifie que l'expansion au-delà de 999,999,999
Consiste simplement à ajouter une ligne supplémentaire à Xlat
, si facile que je l'ai fait. pour toi.
La dernière return
dans Xlat
ne nécessite pas de condition, car la plus grande valeur que vous puissiez détenir dans une signature longue de 64 bits ne représente qu'environ 9,2 quintillions.
Mais si, pour des raisons étranges, Oracle décide d’ajouter un type longer
ou un type damn_long
1024 bits, vous serez prêt pour cela :-)
Et, enfin, un petit test de harnais que vous pouvez utiliser pour valider la fonctionnalité.
public static void main(String[] args) {
long vals[] = {
999L, 1000L, 5821L, 10500L, 101800L, 2000000L,
7800000L, 92150000L, 123200000L, 999999999L,
1000000000L, 1100000000L, 999999999999L,
1000000000000L, 999999999999999L,
1000000000000000L, 9223372036854775807L
};
for (long val: vals)
System.out.println ("" + val + " -> " + Xlat(val));
}
}
Vous pouvez voir dans la sortie que cela vous donne ce dont vous avez besoin:
999 -> 999
1000 -> 1k
5821 -> 5.8k
10500 -> 10k
101800 -> 101k
2000000 -> 2m
7800000 -> 7.8m
92150000 -> 92m
123200000 -> 123m
999999999 -> 999m
1000000000 -> 1b
1100000000 -> 1.1b
999999999999 -> 999b
1000000000000 -> 1t
999999999999999 -> 999t
1000000000000000 -> 1q
9223372036854775807 -> 9.2u
De plus, sachez que passer un nombre négatif à cette fonction entraînera une chaîne trop longue pour vos besoins, car elle suit le chemin
< THOU
). Je pensais que ça allait puisque vous ne mentionnez que des valeurs non négatives dans la question.
Pour quiconque veut arrondir. C'est une excellente solution, facile à lire, qui tire parti de la bibliothèque Java.Lang.Math.
public static String formatNumberExample(Number number) {
char[] suffix = {' ', 'k', 'M', 'B', 'T', 'P', 'E'};
long numValue = number.longValue();
int value = (int) Math.floor(Math.log10(numValue));
int base = value / 3;
if (value >= 3 && base < suffix.length) {
return new DecimalFormat("~#0.0").format(numValue / Math.pow(10, base * 3)) + suffix[base];
} else {
return new DecimalFormat("#,##0").format(numValue);
}
}
Je ne sais pas si c'est la meilleure approche mais, c'est ce que j'ai fait.
7=>7
12=>12
856=>856
1000=>1.0k
5821=>5.82k
10500=>10.5k
101800=>101.8k
2000000=>2.0m
7800000=>7.8m
92150000=>92.15m
123200000=>123.2m
9999999=>10.0m
--- Code ---
public String Format(Integer number){
String[] suffix = new String[]{"k","m","b","t"};
int size = (number.intValue() != 0) ? (int) Math.log10(number) : 0;
if (size >= 3){
while (size % 3 != 0) {
size = size - 1;
}
}
double notation = Math.pow(10, size);
String result = (size >= 3) ? + (Math.round((number / notation) * 100) / 100.0d)+suffix[(size/3) - 1] : + number + "";
return result
}
Mon Java est rouillé, mais voici comment je l'implémenterais en C #:
private string FormatNumber(double value)
{
string[] suffixes = new string[] {" k", " m", " b", " t", " q"};
for (int j = suffixes.Length; j > 0; j--)
{
double unit = Math.Pow(1000, j);
if (value >= unit)
return (value / unit).ToString("#,##0.0") + suffixes[--j];
}
return value.ToString("#,##0");
}
Il serait facile d’ajuster cela en utilisant des kilos CS (1 024) plutôt que des kilos métriques, ou d’ajouter des unités. Il formate 1 000 comme "1,0 k" plutôt que "1 k", mais je suis convaincu que ce n'est pas important.
Pour répondre à l'exigence plus spécifique "pas plus de quatre caractères", supprimez les espaces avant les suffixes et ajustez le bloc du milieu comme suit:
if (value >= unit)
{
value /= unit;
return (value).ToString(value >= unit * 9.95 ? "#,##0" : "#,##0.0") + suffixes[--j];
}
Mon préféré. Vous pouvez également utiliser "k", etc., comme indicateur de nombre décimal, comme cela est courant dans le domaine électronique. Cela vous donnera un chiffre supplémentaire sans espace supplémentaire
La deuxième colonne essaie d'utiliser autant de chiffres que possible
1000 => 1.0k | 1000
5821 => 5.8k | 5821
10500 => 10k | 10k5
101800 => 101k | 101k
2000000 => 2.0m | 2m
7800000 => 7.8m | 7m8
92150000 => 92m | 92m1
123200000 => 123m | 123m
9999999 => 9.9m | 9m99
C'est le code
public class HTTest {
private static String[] unit = {"u", "k", "m", "g", "t"};
/**
* @param args
*/
public static void main(String[] args) {
int[] numbers = new int[]{1000, 5821, 10500, 101800, 2000000, 7800000, 92150000, 123200000, 9999999};
for(int n : numbers) {
System.out.println(n + " => " + myFormat(n) + " | " + myFormat2(n));
}
}
private static String myFormat(int pN) {
String str = Integer.toString(pN);
int len = str.length ()-1;
if (len <= 3) return str;
int level = len / 3;
int mode = len % 3;
switch (mode) {
case 0: return str.substring(0, 1) + "." + str.substring(1, 2) + unit[level];
case 1: return str.substring(0, 2) + unit[level];
case 2: return str.substring(0, 3) + unit[level];
}
return "how that?";
}
private static String trim1 (String pVal) {
if (pVal.equals("0")) return "";
return pVal;
}
private static String trim2 (String pVal) {
if (pVal.equals("00")) return "";
return pVal.substring(0, 1) + trim1(pVal.substring(1,2));
}
private static String myFormat2(int pN) {
String str = Integer.toString(pN);
int len = str.length () - 1;
if (len <= 3) return str;
int level = len / 3;
int mode = len % 3;
switch (mode) {
case 0: return str.substring(0, 1) + unit[level] + trim2(str.substring(1, 3));
case 2: return str.substring(0, 3) + unit[level];
case 1: return str.substring(0, 2) + unit[level] + trim1(str.substring(2, 3));
}
return "how that?";
}
}
Restant fidèle à mon commentaire selon lequel j'accorderais plus d'importance à la lisibilité qu'à la performance, voici une version où il devrait être clair ce qui se passe (en supposant que vous ayez déjà utilisé BigDecimal
s auparavant) sans commentaire excessif (je crois au code auto-documenté) , sans me soucier de la performance (car je ne peux imaginer un scénario dans lequel vous voudriez le faire tant de millions de fois que la performance devient même une considération).
Cette version:
BigDecimal
s par précision et pour éviter les problèmes d'arrondiHALF_UP
comme dans les testsREQUIRED_PRECISION
)enum
pour définir les seuils, c’est-à-dire qu’il pourrait facilement être ajusté pour utiliser KB/Mo/Go/To au lieu de k/m/b/t, etc., et pourrait bien sûr être étendu au-delà de TRILLION
si nécessaireThreshold.Java:
import Java.math.BigDecimal;
public enum Threshold {
TRILLION("1000000000000", 12, 't', null),
BILLION("1000000000", 9, 'b', TRILLION),
MILLION("1000000", 6, 'm', BILLION),
THOUSAND("1000", 3, 'k', MILLION),
ZERO("0", 0, null, THOUSAND);
private BigDecimal value;
private int zeroes;
protected Character suffix;
private Threshold higherThreshold;
private Threshold(String aValueString, int aNumberOfZeroes, Character aSuffix,
Threshold aThreshold) {
value = new BigDecimal(aValueString);
zeroes = aNumberOfZeroes;
suffix = aSuffix;
higherThreshold = aThreshold;
}
public static Threshold thresholdFor(long aValue) {
return thresholdFor(new BigDecimal(aValue));
}
public static Threshold thresholdFor(BigDecimal aValue) {
for (Threshold eachThreshold : Threshold.values()) {
if (eachThreshold.value.compareTo(aValue) <= 0) {
return eachThreshold;
}
}
return TRILLION; // shouldn't be needed, but you might have to extend the enum
}
public int getNumberOfZeroes() {
return zeroes;
}
public String getSuffix() {
return suffix == null ? "" : "" + suffix;
}
public Threshold getHigherThreshold() {
return higherThreshold;
}
}
NumberShortener.Java:
import Java.math.BigDecimal;
import Java.math.RoundingMode;
public class NumberShortener {
public static final int REQUIRED_PRECISION = 2;
public static BigDecimal toPrecisionWithoutLoss(BigDecimal aBigDecimal,
int aPrecision, RoundingMode aMode) {
int previousScale = aBigDecimal.scale();
int previousPrecision = aBigDecimal.precision();
int newPrecision = Math.max(previousPrecision - previousScale, aPrecision);
return aBigDecimal.setScale(previousScale + newPrecision - previousPrecision,
aMode);
}
private static BigDecimal scaledNumber(BigDecimal aNumber, RoundingMode aMode) {
Threshold threshold = Threshold.thresholdFor(aNumber);
BigDecimal adjustedNumber = aNumber.movePointLeft(threshold.getNumberOfZeroes());
BigDecimal scaledNumber = toPrecisionWithoutLoss(adjustedNumber, REQUIRED_PRECISION,
aMode).stripTrailingZeros();
// System.out.println("Number: <" + aNumber + ">, adjusted: <" + adjustedNumber
// + ">, rounded: <" + scaledNumber + ">");
return scaledNumber;
}
public static String shortenedNumber(long aNumber, RoundingMode aMode) {
boolean isNegative = aNumber < 0;
BigDecimal numberAsBigDecimal = new BigDecimal(isNegative ? -aNumber : aNumber);
Threshold threshold = Threshold.thresholdFor(numberAsBigDecimal);
BigDecimal scaledNumber = aNumber == 0 ? numberAsBigDecimal : scaledNumber(
numberAsBigDecimal, aMode);
if (scaledNumber.compareTo(new BigDecimal("1000")) >= 0) {
scaledNumber = scaledNumber(scaledNumber, aMode);
threshold = threshold.getHigherThreshold();
}
String sign = isNegative ? "-" : "";
String printNumber = sign + scaledNumber.stripTrailingZeros().toPlainString()
+ threshold.getSuffix();
// System.out.println("Number: <" + sign + numberAsBigDecimal + ">, rounded: <"
// + sign + scaledNumber + ">, print: <" + printNumber + ">");
return printNumber;
}
}
(Décommentez les instructions println
ou changez pour utiliser votre enregistreur préféré pour voir ce qu'il fait.)
Et enfin, les tests dans NumberShortenerTest (en clair JUnit 4):
import static org.junit.Assert.*;
import Java.math.BigDecimal;
import Java.math.RoundingMode;
import org.junit.Test;
public class NumberShortenerTest {
private static final long[] NUMBERS_FROM_OP = new long[] { 1000, 5821, 10500, 101800, 2000000, 7800000, 92150000, 123200000 };
private static final String[] EXPECTED_FROM_OP = new String[] { "1k", "5.8k", "10k", "101k", "2m", "7.8m", "92m", "123m" };
private static final String[] EXPECTED_FROM_OP_HALF_UP = new String[] { "1k", "5.8k", "11k", "102k", "2m", "7.8m", "92m", "123m" };
private static final long[] NUMBERS_TO_TEST = new long[] { 1, 500, 999, 1000, 1001, 1009, 1049, 1050, 1099, 1100, 12345, 123456, 999999, 1000000,
1000099, 1000999, 1009999, 1099999, 1100000, 1234567, 999999999, 1000000000, 9123456789L, 123456789123L };
private static final String[] EXPECTED_FROM_TEST = new String[] { "1", "500", "999", "1k", "1k", "1k", "1k", "1k", "1k", "1.1k", "12k", "123k",
"999k", "1m", "1m", "1m", "1m", "1m", "1.1m", "1.2m", "999m", "1b", "9.1b", "123b" };
private static final String[] EXPECTED_FROM_TEST_HALF_UP = new String[] { "1", "500", "999", "1k", "1k", "1k", "1k", "1.1k", "1.1k", "1.1k", "12k",
"123k", "1m", "1m", "1m", "1m", "1m", "1.1m", "1.1m", "1.2m", "1b", "1b", "9.1b", "123b" };
@Test
public void testThresholdFor() {
assertEquals(Threshold.ZERO, Threshold.thresholdFor(1));
assertEquals(Threshold.ZERO, Threshold.thresholdFor(999));
assertEquals(Threshold.THOUSAND, Threshold.thresholdFor(1000));
assertEquals(Threshold.THOUSAND, Threshold.thresholdFor(1234));
assertEquals(Threshold.THOUSAND, Threshold.thresholdFor(9999));
assertEquals(Threshold.THOUSAND, Threshold.thresholdFor(999999));
assertEquals(Threshold.MILLION, Threshold.thresholdFor(1000000));
}
@Test
public void testToPrecision() {
RoundingMode mode = RoundingMode.DOWN;
assertEquals(new BigDecimal("1"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 1, mode));
assertEquals(new BigDecimal("1.2"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 2, mode));
assertEquals(new BigDecimal("1.23"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 3, mode));
assertEquals(new BigDecimal("1.234"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 4, mode));
assertEquals(new BigDecimal("999").toPlainString(), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("999"), 4, mode).stripTrailingZeros()
.toPlainString());
assertEquals(new BigDecimal("999").toPlainString(), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("999"), 2, mode).stripTrailingZeros()
.toPlainString());
assertEquals(new BigDecimal("999").toPlainString(), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("999.9"), 2, mode).stripTrailingZeros()
.toPlainString());
mode = RoundingMode.HALF_UP;
assertEquals(new BigDecimal("1"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 1, mode));
assertEquals(new BigDecimal("1.2"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 2, mode));
assertEquals(new BigDecimal("1.23"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 3, mode));
assertEquals(new BigDecimal("1.235"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 4, mode));
assertEquals(new BigDecimal("999").toPlainString(), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("999"), 4, mode).stripTrailingZeros()
.toPlainString());
assertEquals(new BigDecimal("999").toPlainString(), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("999"), 2, mode).stripTrailingZeros()
.toPlainString());
assertEquals(new BigDecimal("1000").toPlainString(), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("999.9"), 2, mode)
.stripTrailingZeros().toPlainString());
}
@Test
public void testNumbersFromOP() {
for (int i = 0; i < NUMBERS_FROM_OP.length; i++) {
assertEquals("Index " + i + ": " + NUMBERS_FROM_OP[i], EXPECTED_FROM_OP[i],
NumberShortener.shortenedNumber(NUMBERS_FROM_OP[i], RoundingMode.DOWN));
assertEquals("Index " + i + ": " + NUMBERS_FROM_OP[i], EXPECTED_FROM_OP_HALF_UP[i],
NumberShortener.shortenedNumber(NUMBERS_FROM_OP[i], RoundingMode.HALF_UP));
}
}
@Test
public void testBorders() {
assertEquals("Zero: " + 0, "0", NumberShortener.shortenedNumber(0, RoundingMode.DOWN));
assertEquals("Zero: " + 0, "0", NumberShortener.shortenedNumber(0, RoundingMode.HALF_UP));
for (int i = 0; i < NUMBERS_TO_TEST.length; i++) {
assertEquals("Index " + i + ": " + NUMBERS_TO_TEST[i], EXPECTED_FROM_TEST[i],
NumberShortener.shortenedNumber(NUMBERS_TO_TEST[i], RoundingMode.DOWN));
assertEquals("Index " + i + ": " + NUMBERS_TO_TEST[i], EXPECTED_FROM_TEST_HALF_UP[i],
NumberShortener.shortenedNumber(NUMBERS_TO_TEST[i], RoundingMode.HALF_UP));
}
}
@Test
public void testNegativeBorders() {
for (int i = 0; i < NUMBERS_TO_TEST.length; i++) {
assertEquals("Index " + i + ": -" + NUMBERS_TO_TEST[i], "-" + EXPECTED_FROM_TEST[i],
NumberShortener.shortenedNumber(-NUMBERS_TO_TEST[i], RoundingMode.DOWN));
assertEquals("Index " + i + ": -" + NUMBERS_TO_TEST[i], "-" + EXPECTED_FROM_TEST_HALF_UP[i],
NumberShortener.shortenedNumber(-NUMBERS_TO_TEST[i], RoundingMode.HALF_UP));
}
}
}
N'hésitez pas à indiquer dans les commentaires si j'ai manqué un cas de test important ou si les valeurs attendues doivent être ajustées.
Ma fonction pour convertir un grand nombre en petit nombre (avec 2 chiffres). Vous pouvez modifier le nombre de chiffres par modification #.##
dans DecimalFormat
public String formatValue(float value) {
String arr[] = {"", "K", "M", "B", "T", "P", "E"};
int index = 0;
while ((value / 1000) >= 1) {
value = value / 1000;
index++;
}
DecimalFormat decimalFormat = new DecimalFormat("#.##");
return String.format("%s %s", decimalFormat.format(value), arr[index]);
}
Test
System.out.println(formatValue(100)); // 100
System.out.println(formatValue(1000)); // 1 K
System.out.println(formatValue(10345)); // 10.35 K
System.out.println(formatValue(10012)); // 10.01 K
System.out.println(formatValue(123456)); // 123.46 K
System.out.println(formatValue(4384324)); // 4.38 M
System.out.println(formatValue(10000000)); // 10 M
System.out.println(formatValue(Long.MAX_VALUE)); // 9.22 E
J'espère que ça aide
Ajout de ma propre réponse, Java, code explicatif ..).
import Java.math.BigDecimal;
/**
* Method to convert number to formatted number.
*
* @author Gautham PJ
*/
public class ShortFormatNumbers
{
/**
* Main method. Execution starts here.
*/
public static void main(String[] args)
{
// The numbers that are being converted.
int[] numbers = {999, 1400, 2500, 45673463, 983456, 234234567};
// Call the "formatNumber" method on individual numbers to format
// the number.
for(int number : numbers)
{
System.out.println(number + ": " + formatNumber(number));
}
}
/**
* Format the number to display it in short format.
*
* The number is divided by 1000 to find which denomination to be added
* to the number. Dividing the number will give the smallest possible
* value with the denomination.
*
* @param the number that needs to be converted to short hand notation.
* @return the converted short hand notation for the number.
*/
private static String formatNumber(double number)
{
String[] denominations = {"", "k", "m", "b", "t"};
int denominationIndex = 0;
// If number is greater than 1000, divide the number by 1000 and
// increment the index for the denomination.
while(number > 1000.0)
{
denominationIndex++;
number = number / 1000.0;
}
// To round it to 2 digits.
BigDecimal bigDecimal = new BigDecimal(number);
bigDecimal = bigDecimal.setScale(2, BigDecimal.ROUND_HALF_EVEN);
// Add the number with the denomination to get the final value.
String formattedNumber = bigDecimal + denominations[denominationIndex];
return formattedNumber;
}
}
c'est mon code. propre et simple.
public static String getRoughNumber(long value) {
if (value <= 999) {
return String.valueOf(value);
}
final String[] units = new String[]{"", "K", "M", "B", "P"};
int digitGroups = (int) (Math.log10(value) / Math.log10(1000));
return new DecimalFormat("#,##0.#").format(value / Math.pow(1000, digitGroups)) + "" + units[digitGroups];
}
essaye ça :
public String Format(Integer number){
String[] suffix = new String[]{"k","m","b","t"};
int size = (number.intValue() != 0) ? (int) Math.log10(number) : 0;
if (size >= 3){
while (size % 3 != 0) {
size = size - 1;
}
}
double notation = Math.pow(10, size);
String result = (size >= 3) ? + (Math.round((number / notation) * 100) / 100.0d)+suffix[(size/3) - 1] : + number + "";
return result
}
Cet extrait de code est simplement mortel, du code épuré et fonctionne parfaitement:
private static char[] c = new char[]{'K', 'M', 'B', 'T'};
private String formatK(double n, int iteration) {
if (n < 1000) {
// print 999 or 999K
if (iteration <= 0) {
return String.valueOf((long) n);
} else {
return String.format("%d%s", Math.round(n), c[iteration-1]);
}
} else if (n < 10000) {
// Print 9.9K
return String.format("%.1f%s", n/1000, c[iteration]);
} else {
// Increase 1 iteration
return formatK(Math.round(n/1000), iteration+1);
}
}
//code longer but work sure...
public static String formatK(int number) {
if (number < 999) {
return String.valueOf(number);
}
if (number < 9999) {
String strNumber = String.valueOf(number);
String str1 = strNumber.substring(0, 1);
String str2 = strNumber.substring(1, 2);
if (str2.equals("0")) {
return str1 + "k";
} else {
return str1 + "." + str2 + "k";
}
}
if (number < 99999) {
String strNumber = String.valueOf(number);
String str1 = strNumber.substring(0, 2);
return str1 + "k";
}
if (number < 999999) {
String strNumber = String.valueOf(number);
String str1 = strNumber.substring(0, 3);
return str1 + "k";
}
if (number < 9999999) {
String strNumber = String.valueOf(number);
String str1 = strNumber.substring(0, 1);
String str2 = strNumber.substring(1, 2);
if (str2.equals("0")) {
return str1 + "m";
} else {
return str1 + "." + str2 + "m";
}
}
if (number < 99999999) {
String strNumber = String.valueOf(number);
String str1 = strNumber.substring(0, 2);
return str1 + "m";
}
if (number < 999999999) {
String strNumber = String.valueOf(number);
String str1 = strNumber.substring(0, 3);
return str1 + "m";
}
NumberFormat formatterHasDigi = new DecimalFormat("###,###,###");
return formatterHasDigi.format(number);
}
public class NumberToReadableWordFormat {
public static void main(String[] args) {
Integer[] numbers = new Integer[]{1000, 5821, 10500, 101800, 2000000, 7800000, 92150000, 123200000, 9999999,999};
for(int n : numbers) {
System.out.println(n + " => " + coolFormat(n));
}
}
private static String[] c = new String[]{"K", "L", "Cr"};
private static String coolFormat(int n) {
int size = String.valueOf(n).length();
if (size>=4 && size<6) {
int value = (int) Math.pow(10, 1);
double d = (double) Math.round(n/1000.0 * value) / value;
return (double) Math.round(n/1000.0 * value) / value+" "+c[0];
} else if(size>5 && size<8) {
int value = (int) Math.pow(10, 1);
return (double) Math.round(n/100000.0 * value) / value+" "+c[1];
} else if(size>=8) {
int value = (int) Math.pow(10, 1);
return (double) Math.round(n/10000000.0 * value) / value+" "+c[2];
} else {
return n+"";
}
}
}
Sortie:
1000 => 1.0 K
5821 => 5.8 K
10500 => 10.5 K
101800 => 1.0 L
2000000 => 20.0 L
7800000 => 78.0 L
92150000 => 9.2 Cr
123200000 => 12.3 Cr
9999999 => 100.0 L
999 => 999