Merge pull request #1134 from akiyamaneko/factorial_improved

优化阶乘相关的逻辑
This commit is contained in:
Golden Looly 2020-09-28 19:00:53 +08:00 committed by GitHub
commit d775ce9cca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 323 additions and 288 deletions

View File

@ -43,6 +43,14 @@ public class NumberUtil {
*/ */
private static final int DEFAUT_DIV_SCALE = 10; 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 结果 * @return 结果
* @since 4.1.0 * @since 4.1.0
*/ */
public static long factorial(long start, long end) { public static long factorial(long start, long end) {
if (0L == start || start == end) { // 负数没有阶乘
return 1L; if(start < 0 || end < 0) {
} throw new IllegalArgumentException(String.format("Factorial start and end both must be >= 0, " +
if (start < end) { "but got start=%d, end=%d", start, end));
return 0L; }
} if (0L == start || start == end) {
return start * factorial(start - 1, end); return 1L;
} }
if (start < end) {
return 0L;
}
return factorialMultiplyAndCheck(start, factorial(start - 1, end));
}
/** /**
* 计算阶乘 * 计算范围阶乘中校验中间的计算是否存在溢出factorial提前做了负数和0的校验因此这里没有校验数字的正负
* <p> * @param a 乘数
* n! = n * (n-1) * ... * 2 * 1 * @param b 被乘数
* </p> * @return 如果 a * b的结果没有溢出直接返回否则抛出异常
* */
* @param n 阶乘起始 private static long factorialMultiplyAndCheck(long a, long b) {
* @return 结果 if (a <= Long.MAX_VALUE / b) {
*/ return a * b;
public static long factorial(long n) { }
return factorial(n, 1); throw new IllegalArgumentException(String.format("Overflow in multiplication: {%d} * {%d}", a, b));
} }
/**
* 计算阶乘
* <p>
* n! = n * (n-1) * ... * 2 * 1
* </p>
*
* @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];
}
/** /**
* 平方根算法<br> * 平方根算法<br>

View File

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