修复NumberWordFormatterformatSimple输出错误问题(pr#4034@Github)

This commit is contained in:
Looly
2025-08-26 17:32:44 +08:00
parent 99adfecd1a
commit d89571f82d
9 changed files with 112 additions and 38 deletions

View File

@@ -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;
/**

View File

@@ -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();

View File

@@ -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;
/** 正在执行的定时任务 */

View File

@@ -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;
/**

View File

@@ -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";

View File

@@ -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>

View File

@@ -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");

View File

@@ -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);
}
}

View File

@@ -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;
/**