mirror of
https://gitee.com/dromara/hutool.git
synced 2026-02-09 09:16:26 +08:00
修复NumberWordFormatterformatSimple输出错误问题(pr#4034@Github)
This commit is contained in:
@@ -18,7 +18,6 @@ package cn.hutool.v7.core.cache.impl;
|
||||
|
||||
import cn.hutool.v7.core.cache.Cache;
|
||||
import cn.hutool.v7.core.cache.CacheListener;
|
||||
import cn.hutool.v7.core.func.SerSupplier;
|
||||
import cn.hutool.v7.core.lang.mutable.Mutable;
|
||||
import cn.hutool.v7.core.lang.mutable.MutableObj;
|
||||
|
||||
@@ -26,10 +25,7 @@ import java.io.Serial;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.LongAdder;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
|
||||
@@ -80,7 +80,7 @@ public abstract class LockedCache<K, V> extends AbstractCache<K, V> {
|
||||
if (null == co) {
|
||||
// supplier的创建是一个耗时过程,此处创建与全局锁无关,而与key锁相关,这样就保证每个key只创建一个value,且互斥
|
||||
v = valueFactory.get();
|
||||
put(key, v, timeout);
|
||||
putWithoutLock(key, v, timeout);
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
|
||||
@@ -21,6 +21,7 @@ import cn.hutool.v7.core.lang.Assert;
|
||||
import cn.hutool.v7.core.lang.mutable.Mutable;
|
||||
import cn.hutool.v7.core.thread.lock.NoLock;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -40,6 +41,7 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
* @param <V> 值类型
|
||||
*/
|
||||
public class TimedCache<K, V> extends LockedCache<K, V> {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 正在执行的定时任务 */
|
||||
|
||||
@@ -22,6 +22,8 @@ import cn.hutool.v7.core.lang.mutable.Mutable;
|
||||
import cn.hutool.v7.core.lang.ref.Ref;
|
||||
import cn.hutool.v7.core.map.reference.WeakConcurrentMap;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* 弱引用缓存<br>
|
||||
* 对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。<br>
|
||||
@@ -35,6 +37,7 @@ import cn.hutool.v7.core.map.reference.WeakConcurrentMap;
|
||||
* @since 3.0.7
|
||||
*/
|
||||
public class WeakCache<K, V> extends TimedCache<K, V>{
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,7 +35,10 @@ public class EnglishNumberFormatter {
|
||||
"SEVENTY", "EIGHTY", "NINETY"};
|
||||
private static final String[] NUMBER_MORE = new String[]{"", "THOUSAND", "MILLION", "BILLION", "TRILLION"};
|
||||
|
||||
private static final String[] NUMBER_SUFFIX = new String[]{"k", "w", "", "m", "", "", "b", "", "", "t", "", "", "p", "", "", "e"};
|
||||
// private static final String[] NUMBER_SUFFIX = new String[]{"k", "w", "", "m", "", "", "b", "", "", "t", "", "", "p", "", "", "e"};
|
||||
private static final String[] NUMBER_SUFFIX = new String[] { "k", "w", "m", "b", "t", "p", "e" };
|
||||
// 标准单位序列(k, m, b, t, p, e)在NUMBER_SUFFIX中的索引
|
||||
private static final int[] STANDARD_UNIT_INDICES = { 0, 2, 3, 4, 5, 6 };
|
||||
|
||||
/**
|
||||
* 将阿拉伯数字转为英文表达式
|
||||
@@ -64,30 +67,53 @@ public class EnglishNumberFormatter {
|
||||
}
|
||||
|
||||
/**
|
||||
* 将阿拉伯数字转化为简介计数单位,例如 2100 =》 2.1k
|
||||
* 将数字转换为简写形式,格式单位简写为:k(千), m(百万), b(十亿), t(万亿),如果中文单位,为w(万)
|
||||
* <ul>
|
||||
* <li>1000 =》 1k</li>
|
||||
* <li>10000 =》 10k,如果是中文单位,则为1w</li>
|
||||
* <li>100000 =》 100k,如果是中文单位,则为10w</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param value 对应数字的值
|
||||
* @param isTwo 控制是否为只为k、w,例如当为{@code false}时返回4.38m,{@code true}返回438.43w
|
||||
* @return 格式化后的数字
|
||||
* @since 5.5.9
|
||||
* @param number 要转换的数字
|
||||
* @param useChineseUnit 是否使用中文单位(w等)
|
||||
* @return 简写形式的字符串
|
||||
*/
|
||||
public static String formatSimple(final long value, final boolean isTwo) {
|
||||
if (value < 1000) {
|
||||
return String.valueOf(value);
|
||||
public static String formatSimple(final long number, final boolean useChineseUnit) {
|
||||
if (number < 1_000) {
|
||||
return String.valueOf(number);
|
||||
}
|
||||
int index = -1;
|
||||
double res = value;
|
||||
while (res > 10 && (!isTwo || index < 1)) {
|
||||
if (res >= 1000) {
|
||||
res = res / 1000;
|
||||
index++;
|
||||
}
|
||||
if (res > 10) {
|
||||
res = res / 10;
|
||||
index++;
|
||||
|
||||
double value;
|
||||
String suffix;
|
||||
|
||||
// 使用国际单位系统:k(千), m(百万), b(十亿), t(万亿)
|
||||
if (number < 1_000_000) {
|
||||
value = number / 1_000.0;
|
||||
suffix = "k";
|
||||
} else if (number < 1_000_000_000) {
|
||||
value = number / 1_000_000.0;
|
||||
suffix = "m";
|
||||
} else if (number < 1_000_000_000_000L) {
|
||||
value = number / 1_000_000_000.0;
|
||||
suffix = "b";
|
||||
} else {
|
||||
value = number / 1_000_000_000_000.0;
|
||||
suffix = "t";
|
||||
}
|
||||
|
||||
// 兼容中文简写形式,如10k->1w
|
||||
if (useChineseUnit) {
|
||||
if("m".equals(suffix)){
|
||||
suffix = "w";
|
||||
value *= 100;
|
||||
} else if("k".equals(suffix) && value >= 10){
|
||||
suffix = "w";
|
||||
value /= 10;
|
||||
}
|
||||
}
|
||||
return String.format("%s%s", NumberUtil.format("#.##", res), NUMBER_SUFFIX[index]);
|
||||
|
||||
// 格式化数字,最多保留2位小数,去除尾部的0
|
||||
return NumberUtil.format("#.##", value) + suffix;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,9 +123,9 @@ public class EnglishNumberFormatter {
|
||||
* @return 英文表达式
|
||||
*/
|
||||
private static String format(final String x) {
|
||||
final int z = x.indexOf("."); // 取小数点位置
|
||||
final int z = x.indexOf(StrUtil.DOT); // 取小数点位置
|
||||
final String lstr;
|
||||
String rstr = "";
|
||||
String rstr = StrUtil.EMPTY;
|
||||
if (z > -1) { // 看是否有小数,如果有,则分别取左边和右边
|
||||
lstr = x.substring(0, z);
|
||||
rstr = x.substring(z + 1);
|
||||
@@ -125,7 +151,7 @@ public class EnglishNumberFormatter {
|
||||
if (!"000".equals(a[i])) { // 用来避免这种情况:1000000 = one million
|
||||
// thousand only
|
||||
if (i != 0) {
|
||||
lm.insert(0, transThree(a[i]) + " " + parseMore(i) + " "); // 加:
|
||||
lm.insert(0, transThree(a[i]) + StrUtil.SPACE + parseMore(i) + StrUtil.SPACE); // 加:
|
||||
// thousand、million、billion
|
||||
} else {
|
||||
// 防止i=0时, 在多加两个空格.
|
||||
@@ -136,9 +162,9 @@ public class EnglishNumberFormatter {
|
||||
}
|
||||
}
|
||||
|
||||
String xs = lm.length() == 0 ? "ZERO " : " "; // 用来存放转换后小数部分
|
||||
String xs = lm.isEmpty() ? "ZERO " : StrUtil.SPACE; // 用来存放转换后小数部分
|
||||
if (z > -1) {
|
||||
xs += "AND CENTS " + transTwo(rstr) + " "; // 小数部分存在时转换小数
|
||||
xs += "AND CENTS " + transTwo(rstr) + StrUtil.SPACE; // 小数部分存在时转换小数
|
||||
}
|
||||
|
||||
return lm.toString().trim() + xs + "ONLY";
|
||||
|
||||
@@ -472,6 +472,7 @@ public class NumberUtil extends NumberValidator {
|
||||
* <li>00.000 =》 取两位整数和三位小数</li>
|
||||
* <li># =》 取所有整数部分</li>
|
||||
* <li>#.##% =》 以百分比方式计数,并取两位小数</li>
|
||||
* <li>#.## =》 取两位小数,小数部分为0时忽略</li>
|
||||
* <li>#.#####E0 =》 显示为科学计数法,并取五位小数</li>
|
||||
* <li>,### =》 每三位以逗号进行分隔,例如:299,792,458</li>
|
||||
* <li>光速大小为每秒,###米 =》 将格式嵌入文本</li>
|
||||
|
||||
@@ -148,7 +148,7 @@ public class CacheTest {
|
||||
* https://gitee.com/chinabugotech/hutool/issues/IBP752
|
||||
*/
|
||||
@Test
|
||||
public void whenContainsKeyTimeout_shouldCallOnRemove() {
|
||||
public void whenContainsKeyTimeoutShouldCallOnRemove() {
|
||||
final int timeout = 50;
|
||||
final TimedCache<Integer, String> ALARM_CACHE = new TimedCache<>(timeout);
|
||||
|
||||
@@ -170,9 +170,9 @@ public class CacheTest {
|
||||
* https://github.com/chinabugotech/hutool/issues/3957
|
||||
*/
|
||||
@Test
|
||||
public void reentrantCache_clear_Method_Test() {
|
||||
AtomicInteger removeCount = new AtomicInteger();
|
||||
Cache<String, String> lruCache = CacheUtil.newLRUCache(4);
|
||||
public void reentrantCacheClearMethodTest() {
|
||||
final AtomicInteger removeCount = new AtomicInteger();
|
||||
final Cache<String, String> lruCache = CacheUtil.newLRUCache(4);
|
||||
lruCache.setListener((key, cachedObject) -> removeCount.getAndIncrement());
|
||||
lruCache.put("key1","String1");
|
||||
lruCache.put("key2","String2");
|
||||
|
||||
@@ -56,4 +56,52 @@ public class NumberWordFormatTest {
|
||||
final String s = EnglishNumberFormatter.formatSimple(1000);
|
||||
Assertions.assertEquals("1k", s);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void issue4033Test(){
|
||||
String s = EnglishNumberFormatter.formatSimple(1_000, false);
|
||||
Assertions.assertEquals("1k", s);
|
||||
|
||||
s = EnglishNumberFormatter.formatSimple(10_000, false);
|
||||
Assertions.assertEquals("10k", s);
|
||||
|
||||
s = EnglishNumberFormatter.formatSimple(100_000, false);
|
||||
Assertions.assertEquals("100k", s);
|
||||
|
||||
s = EnglishNumberFormatter.formatSimple(1_000_000, false);
|
||||
Assertions.assertEquals("1m", s);
|
||||
|
||||
s = EnglishNumberFormatter.formatSimple(10_000_000, false);
|
||||
Assertions.assertEquals("10m", s);
|
||||
|
||||
s = EnglishNumberFormatter.formatSimple(100_000_000, false);
|
||||
Assertions.assertEquals("100m", s);
|
||||
|
||||
s = EnglishNumberFormatter.formatSimple(1_000_000_000, false);
|
||||
Assertions.assertEquals("1b", s);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void issue4033Test2(){
|
||||
String s = EnglishNumberFormatter.formatSimple(1_000, true);
|
||||
Assertions.assertEquals("1k", s);
|
||||
|
||||
s = EnglishNumberFormatter.formatSimple(10_000, true);
|
||||
Assertions.assertEquals("1w", s);
|
||||
|
||||
s = EnglishNumberFormatter.formatSimple(100_000, true);
|
||||
Assertions.assertEquals("10w", s);
|
||||
|
||||
s = EnglishNumberFormatter.formatSimple(1_000_000, true);
|
||||
Assertions.assertEquals("100w", s);
|
||||
|
||||
s = EnglishNumberFormatter.formatSimple(10_000_000, true);
|
||||
Assertions.assertEquals("1000w", s);
|
||||
|
||||
s = EnglishNumberFormatter.formatSimple(100_000_000, true);
|
||||
Assertions.assertEquals("10000w", s);
|
||||
|
||||
s = EnglishNumberFormatter.formatSimple(1_000_000_000, true);
|
||||
Assertions.assertEquals("1b", s);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,10 +30,7 @@ import java.awt.Color;
|
||||
import java.awt.Font;
|
||||
import java.awt.Image;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* 抽象验证码<br>
|
||||
@@ -43,6 +40,7 @@ import java.io.OutputStream;
|
||||
* @author Looly
|
||||
*/
|
||||
public abstract class AbstractCaptcha implements ICaptcha {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 3180820918087507254L;
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user