diff --git a/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java index 3afbec7f3..2f0f95a5e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java @@ -43,6 +43,14 @@ public class NumberUtil { */ private static final int DEFAUT_DIV_SCALE = 10; + /** + * 0-20对应的阶乘,超过20的阶乘会超过Long.MAX_VALUE + */ + private static final long[] FACTORIALS = new long[]{ + 1L, 1L, 2L, 6L, 24L, 120L, 720L, 5040L, 40320L, 362880L, 3628800L, 39916800L, 479001600L, 6227020800L, + 87178291200L, 1307674368000L, 20922789888000L, 355687428096000L, 6402373705728000L, 121645100408832000L, + 2432902008176640000L}; + /** * 提供精确的加法运算 * @@ -1422,28 +1430,50 @@ public class NumberUtil { * @return 结果 * @since 4.1.0 */ - public static long factorial(long start, long end) { - if (0L == start || start == end) { - return 1L; - } - if (start < end) { - return 0L; - } - return start * factorial(start - 1, end); - } - + public static long factorial(long start, long end) { + // 负数没有阶乘 + if(start < 0 || end < 0) { + throw new IllegalArgumentException(String.format("Factorial start and end both must be >= 0, " + + "but got start=%d, end=%d", start, end)); + } + if (0L == start || start == end) { + return 1L; + } + if (start < end) { + return 0L; + } + return factorialMultiplyAndCheck(start, factorial(start - 1, end)); + } + /** - * 计算阶乘 - *

- * n! = n * (n-1) * ... * 2 * 1 - *

- * - * @param n 阶乘起始 - * @return 结果 - */ - public static long factorial(long n) { - return factorial(n, 1); - } + * 计算范围阶乘中校验中间的计算是否存在溢出,factorial提前做了负数和0的校验,因此这里没有校验数字的正负 + * @param a 乘数 + * @param b 被乘数 + * @return 如果 a * b的结果没有溢出直接返回,否则抛出异常 + */ + private static long factorialMultiplyAndCheck(long a, long b) { + if (a <= Long.MAX_VALUE / b) { + return a * b; + } + throw new IllegalArgumentException(String.format("Overflow in multiplication: {%d} * {%d}", a, b)); + } + + /** + * 计算阶乘 + *

+ * n! = n * (n-1) * ... * 2 * 1 + *

+ * + * @param n 阶乘起始 + * @return 结果 + */ + public static long factorial(long n) { + if (n < 0 || n > 20) { + throw new IllegalArgumentException(String.format("Factorial must have n >= 0 and n <= 20 for n!, " + + "but got n = %d", n)); + } + return FACTORIALS[(int) n]; + } /** * 平方根算法
diff --git a/hutool-core/src/test/java/cn/hutool/core/util/NumberUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/NumberUtilTest.java index 100336c3c..dd1710807 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/NumberUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/NumberUtilTest.java @@ -1,267 +1,272 @@ -package cn.hutool.core.util; - -import org.junit.Assert; -import org.junit.Test; - -import java.math.BigDecimal; -import java.math.RoundingMode; - -/** - * {@link NumberUtil} 单元测试类 - * - * @author Looly - * - */ -public class NumberUtilTest { - - @Test - public void addTest() { - Float a = 3.15f; - Double b = 4.22; - double result = NumberUtil.add(a, b).doubleValue(); - Assert.assertEquals(7.37, result, 2); - } - - @Test - public void addTest2() { - double a = 3.15f; - double b = 4.22; - double result = NumberUtil.add(a, b); - Assert.assertEquals(7.37, result, 2); - } - - @Test - public void addTest3() { - float a = 3.15f; - double b = 4.22; - double result = NumberUtil.add(a, b, a, b).doubleValue(); - Assert.assertEquals(14.74, result, 2); - } - - @Test - public void addTest4() { - BigDecimal result = NumberUtil.add(new BigDecimal("133"), new BigDecimal("331")); - Assert.assertEquals(new BigDecimal("464"), result); - } - - @Test - public void isIntegerTest() { - Assert.assertTrue(NumberUtil.isInteger("-12")); - Assert.assertTrue(NumberUtil.isInteger("256")); - Assert.assertTrue(NumberUtil.isInteger("0256")); - Assert.assertTrue(NumberUtil.isInteger("0")); - Assert.assertFalse(NumberUtil.isInteger("23.4")); - } - - @Test - public void isLongTest() { - Assert.assertTrue(NumberUtil.isLong("-12")); - Assert.assertTrue(NumberUtil.isLong("256")); - Assert.assertTrue(NumberUtil.isLong("0256")); - Assert.assertTrue(NumberUtil.isLong("0")); - Assert.assertFalse(NumberUtil.isLong("23.4")); - } - - @Test - public void isNumberTest() { - Assert.assertTrue(NumberUtil.isNumber("28.55")); - Assert.assertTrue(NumberUtil.isNumber("0")); - Assert.assertTrue(NumberUtil.isNumber("+100.10")); - Assert.assertTrue(NumberUtil.isNumber("-22.022")); - Assert.assertTrue(NumberUtil.isNumber("0X22")); - } - - @Test - public void divTest() { - double result = NumberUtil.div(0, 1); - Assert.assertEquals(0.0, result, 0); - } - - @Test - public void roundTest() { - - // 四舍 - String round1 = NumberUtil.roundStr(2.674, 2); - String round2 = NumberUtil.roundStr("2.674", 2); - Assert.assertEquals("2.67", round1); - Assert.assertEquals("2.67", round2); - - // 五入 - String round3 = NumberUtil.roundStr(2.675, 2); - String round4 = NumberUtil.roundStr("2.675", 2); - Assert.assertEquals("2.68", round3); - Assert.assertEquals("2.68", round4); - - // 四舍六入五成双 - String round31 = NumberUtil.roundStr(4.245, 2, RoundingMode.HALF_EVEN); - String round41 = NumberUtil.roundStr("4.2451", 2, RoundingMode.HALF_EVEN); - Assert.assertEquals("4.24", round31); - Assert.assertEquals("4.25", round41); - - // 补0 - String round5 = NumberUtil.roundStr(2.6005, 2); - String round6 = NumberUtil.roundStr("2.6005", 2); - Assert.assertEquals("2.60", round5); - Assert.assertEquals("2.60", round6); - - // 补0 - String round7 = NumberUtil.roundStr(2.600, 2); - String round8 = NumberUtil.roundStr("2.600", 2); - Assert.assertEquals("2.60", round7); - Assert.assertEquals("2.60", round8); - } - - @Test - public void roundStrTest() { - String roundStr = NumberUtil.roundStr(2.647, 2); - Assert.assertEquals(roundStr, "2.65"); - } - - @Test - public void roundHalfEvenTest() { - String roundStr = NumberUtil.roundHalfEven(4.245, 2).toString(); - Assert.assertEquals(roundStr, "4.24"); - roundStr = NumberUtil.roundHalfEven(4.2450, 2).toString(); - Assert.assertEquals(roundStr, "4.24"); - roundStr = NumberUtil.roundHalfEven(4.2451, 2).toString(); - Assert.assertEquals(roundStr, "4.25"); - roundStr = NumberUtil.roundHalfEven(4.2250, 2).toString(); - Assert.assertEquals(roundStr, "4.22"); - - roundStr = NumberUtil.roundHalfEven(1.2050, 2).toString(); - Assert.assertEquals(roundStr, "1.20"); - roundStr = NumberUtil.roundHalfEven(1.2150, 2).toString(); - Assert.assertEquals(roundStr, "1.22"); - roundStr = NumberUtil.roundHalfEven(1.2250, 2).toString(); - Assert.assertEquals(roundStr, "1.22"); - roundStr = NumberUtil.roundHalfEven(1.2350, 2).toString(); - Assert.assertEquals(roundStr, "1.24"); - roundStr = NumberUtil.roundHalfEven(1.2450, 2).toString(); - Assert.assertEquals(roundStr, "1.24"); - roundStr = NumberUtil.roundHalfEven(1.2550, 2).toString(); - Assert.assertEquals(roundStr, "1.26"); - roundStr = NumberUtil.roundHalfEven(1.2650, 2).toString(); - Assert.assertEquals(roundStr, "1.26"); - roundStr = NumberUtil.roundHalfEven(1.2750, 2).toString(); - Assert.assertEquals(roundStr, "1.28"); - roundStr = NumberUtil.roundHalfEven(1.2850, 2).toString(); - Assert.assertEquals(roundStr, "1.28"); - roundStr = NumberUtil.roundHalfEven(1.2950, 2).toString(); - Assert.assertEquals(roundStr, "1.30"); - } - - @Test - public void decimalFormatTest() { - long c = 299792458;// 光速 - - String format = NumberUtil.decimalFormat(",###", c); - Assert.assertEquals("299,792,458", format); - } - - @Test - public void decimalFormatMoneyTest() { - double c = 299792400.543534534; - - String format = NumberUtil.decimalFormatMoney(c); - Assert.assertEquals("299,792,400.54", format); - - double value = 0.5; - String money = NumberUtil.decimalFormatMoney(value); - Assert.assertEquals("0.50", money); - } - - @Test - public void equalsTest() { - Assert.assertTrue(NumberUtil.equals(new BigDecimal("0.00"), BigDecimal.ZERO)); - } - - @Test - public void formatPercentTest() { - String str = NumberUtil.formatPercent(0.33543545, 2); - Assert.assertEquals("33.54%", str); - } - - @Test - public void toBigDecimalTest() { - double a = 3.14; - - BigDecimal bigDecimal = NumberUtil.toBigDecimal(a); - Assert.assertEquals("3.14", bigDecimal.toString()); - } - - @Test - public void maxTest() { - int max = NumberUtil.max(5,4,3,6,1); - Assert.assertEquals(6, max); - } - - @Test - public void minTest() { - int min = NumberUtil.min(5,4,3,6,1); - Assert.assertEquals(1, min); - } - - @Test - public void parseIntTest() { - int v1 = NumberUtil.parseInt("0xFF"); - Assert.assertEquals(255, v1); - int v2 = NumberUtil.parseInt("010"); - Assert.assertEquals(10, v2); - int v3 = NumberUtil.parseInt("10"); - Assert.assertEquals(10, v3); - int v4 = NumberUtil.parseInt(" "); - Assert.assertEquals(0, v4); - int v5 = NumberUtil.parseInt("10F"); - Assert.assertEquals(10, v5); - int v6 = NumberUtil.parseInt("22.4D"); - Assert.assertEquals(22, v6); - - int v7 = NumberUtil.parseInt("0"); - Assert.assertEquals(0, v7); - } - - @Test - public void parseLongTest() { - long v1 = NumberUtil.parseLong("0xFF"); - Assert.assertEquals(255L, v1); - long v2 = NumberUtil.parseLong("010"); - Assert.assertEquals(10L, v2); - long v3 = NumberUtil.parseLong("10"); - Assert.assertEquals(10L, v3); - long v4 = NumberUtil.parseLong(" "); - Assert.assertEquals(0L, v4); - long v5 = NumberUtil.parseLong("10F"); - Assert.assertEquals(10L, v5); - long v6 = NumberUtil.parseLong("22.4D"); - Assert.assertEquals(22L, v6); - } - - @Test - public void factorialTest(){ - long factorial = NumberUtil.factorial(0); - Assert.assertEquals(1, factorial); - - factorial = NumberUtil.factorial(5, 0); - Assert.assertEquals(120, factorial); - factorial = NumberUtil.factorial(5, 1); - Assert.assertEquals(120, factorial); - - Assert.assertEquals(5, NumberUtil.factorial(5, 4)); - } - - @Test - public void mulTest(){ - final BigDecimal mul = NumberUtil.mul(new BigDecimal("10"), null); - Assert.assertEquals(BigDecimal.ZERO, mul); - } - - - @Test - public void isPowerOfTwoTest() { - Assert.assertEquals(false, NumberUtil.isPowerOfTwo(-1)); - Assert.assertEquals(true, NumberUtil.isPowerOfTwo(16)); - Assert.assertEquals(true, NumberUtil.isPowerOfTwo(65536)); - Assert.assertEquals(true, NumberUtil.isPowerOfTwo(1)); - Assert.assertEquals(false, NumberUtil.isPowerOfTwo(17)); - } -} +package cn.hutool.core.util; + +import org.junit.Assert; +import org.junit.Test; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +/** + * {@link NumberUtil} 单元测试类 + * + * @author Looly + * + */ +public class NumberUtilTest { + + @Test + public void addTest() { + Float a = 3.15f; + Double b = 4.22; + double result = NumberUtil.add(a, b).doubleValue(); + Assert.assertEquals(7.37, result, 2); + } + + @Test + public void addTest2() { + double a = 3.15f; + double b = 4.22; + double result = NumberUtil.add(a, b); + Assert.assertEquals(7.37, result, 2); + } + + @Test + public void addTest3() { + float a = 3.15f; + double b = 4.22; + double result = NumberUtil.add(a, b, a, b).doubleValue(); + Assert.assertEquals(14.74, result, 2); + } + + @Test + public void addTest4() { + BigDecimal result = NumberUtil.add(new BigDecimal("133"), new BigDecimal("331")); + Assert.assertEquals(new BigDecimal("464"), result); + } + + @Test + public void isIntegerTest() { + Assert.assertTrue(NumberUtil.isInteger("-12")); + Assert.assertTrue(NumberUtil.isInteger("256")); + Assert.assertTrue(NumberUtil.isInteger("0256")); + Assert.assertTrue(NumberUtil.isInteger("0")); + Assert.assertFalse(NumberUtil.isInteger("23.4")); + } + + @Test + public void isLongTest() { + Assert.assertTrue(NumberUtil.isLong("-12")); + Assert.assertTrue(NumberUtil.isLong("256")); + Assert.assertTrue(NumberUtil.isLong("0256")); + Assert.assertTrue(NumberUtil.isLong("0")); + Assert.assertFalse(NumberUtil.isLong("23.4")); + } + + @Test + public void isNumberTest() { + Assert.assertTrue(NumberUtil.isNumber("28.55")); + Assert.assertTrue(NumberUtil.isNumber("0")); + Assert.assertTrue(NumberUtil.isNumber("+100.10")); + Assert.assertTrue(NumberUtil.isNumber("-22.022")); + Assert.assertTrue(NumberUtil.isNumber("0X22")); + } + + @Test + public void divTest() { + double result = NumberUtil.div(0, 1); + Assert.assertEquals(0.0, result, 0); + } + + @Test + public void roundTest() { + + // 四舍 + String round1 = NumberUtil.roundStr(2.674, 2); + String round2 = NumberUtil.roundStr("2.674", 2); + Assert.assertEquals("2.67", round1); + Assert.assertEquals("2.67", round2); + + // 五入 + String round3 = NumberUtil.roundStr(2.675, 2); + String round4 = NumberUtil.roundStr("2.675", 2); + Assert.assertEquals("2.68", round3); + Assert.assertEquals("2.68", round4); + + // 四舍六入五成双 + String round31 = NumberUtil.roundStr(4.245, 2, RoundingMode.HALF_EVEN); + String round41 = NumberUtil.roundStr("4.2451", 2, RoundingMode.HALF_EVEN); + Assert.assertEquals("4.24", round31); + Assert.assertEquals("4.25", round41); + + // 补0 + String round5 = NumberUtil.roundStr(2.6005, 2); + String round6 = NumberUtil.roundStr("2.6005", 2); + Assert.assertEquals("2.60", round5); + Assert.assertEquals("2.60", round6); + + // 补0 + String round7 = NumberUtil.roundStr(2.600, 2); + String round8 = NumberUtil.roundStr("2.600", 2); + Assert.assertEquals("2.60", round7); + Assert.assertEquals("2.60", round8); + } + + @Test + public void roundStrTest() { + String roundStr = NumberUtil.roundStr(2.647, 2); + Assert.assertEquals(roundStr, "2.65"); + } + + @Test + public void roundHalfEvenTest() { + String roundStr = NumberUtil.roundHalfEven(4.245, 2).toString(); + Assert.assertEquals(roundStr, "4.24"); + roundStr = NumberUtil.roundHalfEven(4.2450, 2).toString(); + Assert.assertEquals(roundStr, "4.24"); + roundStr = NumberUtil.roundHalfEven(4.2451, 2).toString(); + Assert.assertEquals(roundStr, "4.25"); + roundStr = NumberUtil.roundHalfEven(4.2250, 2).toString(); + Assert.assertEquals(roundStr, "4.22"); + + roundStr = NumberUtil.roundHalfEven(1.2050, 2).toString(); + Assert.assertEquals(roundStr, "1.20"); + roundStr = NumberUtil.roundHalfEven(1.2150, 2).toString(); + Assert.assertEquals(roundStr, "1.22"); + roundStr = NumberUtil.roundHalfEven(1.2250, 2).toString(); + Assert.assertEquals(roundStr, "1.22"); + roundStr = NumberUtil.roundHalfEven(1.2350, 2).toString(); + Assert.assertEquals(roundStr, "1.24"); + roundStr = NumberUtil.roundHalfEven(1.2450, 2).toString(); + Assert.assertEquals(roundStr, "1.24"); + roundStr = NumberUtil.roundHalfEven(1.2550, 2).toString(); + Assert.assertEquals(roundStr, "1.26"); + roundStr = NumberUtil.roundHalfEven(1.2650, 2).toString(); + Assert.assertEquals(roundStr, "1.26"); + roundStr = NumberUtil.roundHalfEven(1.2750, 2).toString(); + Assert.assertEquals(roundStr, "1.28"); + roundStr = NumberUtil.roundHalfEven(1.2850, 2).toString(); + Assert.assertEquals(roundStr, "1.28"); + roundStr = NumberUtil.roundHalfEven(1.2950, 2).toString(); + Assert.assertEquals(roundStr, "1.30"); + } + + @Test + public void decimalFormatTest() { + long c = 299792458;// 光速 + + String format = NumberUtil.decimalFormat(",###", c); + Assert.assertEquals("299,792,458", format); + } + + @Test + public void decimalFormatMoneyTest() { + double c = 299792400.543534534; + + String format = NumberUtil.decimalFormatMoney(c); + Assert.assertEquals("299,792,400.54", format); + + double value = 0.5; + String money = NumberUtil.decimalFormatMoney(value); + Assert.assertEquals("0.50", money); + } + + @Test + public void equalsTest() { + Assert.assertTrue(NumberUtil.equals(new BigDecimal("0.00"), BigDecimal.ZERO)); + } + + @Test + public void formatPercentTest() { + String str = NumberUtil.formatPercent(0.33543545, 2); + Assert.assertEquals("33.54%", str); + } + + @Test + public void toBigDecimalTest() { + double a = 3.14; + + BigDecimal bigDecimal = NumberUtil.toBigDecimal(a); + Assert.assertEquals("3.14", bigDecimal.toString()); + } + + @Test + public void maxTest() { + int max = NumberUtil.max(5,4,3,6,1); + Assert.assertEquals(6, max); + } + + @Test + public void minTest() { + int min = NumberUtil.min(5,4,3,6,1); + Assert.assertEquals(1, min); + } + + @Test + public void parseIntTest() { + int v1 = NumberUtil.parseInt("0xFF"); + Assert.assertEquals(255, v1); + int v2 = NumberUtil.parseInt("010"); + Assert.assertEquals(10, v2); + int v3 = NumberUtil.parseInt("10"); + Assert.assertEquals(10, v3); + int v4 = NumberUtil.parseInt(" "); + Assert.assertEquals(0, v4); + int v5 = NumberUtil.parseInt("10F"); + Assert.assertEquals(10, v5); + int v6 = NumberUtil.parseInt("22.4D"); + Assert.assertEquals(22, v6); + + int v7 = NumberUtil.parseInt("0"); + Assert.assertEquals(0, v7); + } + + @Test + public void parseLongTest() { + long v1 = NumberUtil.parseLong("0xFF"); + Assert.assertEquals(255L, v1); + long v2 = NumberUtil.parseLong("010"); + Assert.assertEquals(10L, v2); + long v3 = NumberUtil.parseLong("10"); + Assert.assertEquals(10L, v3); + long v4 = NumberUtil.parseLong(" "); + Assert.assertEquals(0L, v4); + long v5 = NumberUtil.parseLong("10F"); + Assert.assertEquals(10L, v5); + long v6 = NumberUtil.parseLong("22.4D"); + Assert.assertEquals(22L, v6); + } + + @Test + public void factorialTest(){ + long factorial = NumberUtil.factorial(0); + Assert.assertEquals(1, factorial); + + Assert.assertEquals(1L, NumberUtil.factorial(1)); + Assert.assertEquals(1307674368000L, NumberUtil.factorial(15)); + Assert.assertEquals(2432902008176640000L, NumberUtil.factorial(20)); + + factorial = NumberUtil.factorial(5, 0); + Assert.assertEquals(120, factorial); + factorial = NumberUtil.factorial(5, 1); + Assert.assertEquals(120, factorial); + + Assert.assertEquals(5, NumberUtil.factorial(5, 4)); + Assert.assertEquals(2432902008176640000L, NumberUtil.factorial(20, 0)); + } + + @Test + public void mulTest(){ + final BigDecimal mul = NumberUtil.mul(new BigDecimal("10"), null); + Assert.assertEquals(BigDecimal.ZERO, mul); + } + + + @Test + public void isPowerOfTwoTest() { + Assert.assertEquals(false, NumberUtil.isPowerOfTwo(-1)); + Assert.assertEquals(true, NumberUtil.isPowerOfTwo(16)); + Assert.assertEquals(true, NumberUtil.isPowerOfTwo(65536)); + Assert.assertEquals(true, NumberUtil.isPowerOfTwo(1)); + Assert.assertEquals(false, NumberUtil.isPowerOfTwo(17)); + } +}