diff --git a/src/api/java/mekanism/api/MekanismAPI.java b/src/api/java/mekanism/api/MekanismAPI.java index 28b1c52fdd2..0b8b86f3931 100644 --- a/src/api/java/mekanism/api/MekanismAPI.java +++ b/src/api/java/mekanism/api/MekanismAPI.java @@ -22,7 +22,7 @@ private MekanismAPI() { /** * The version of the api classes - may not always match the mod's version */ - public static final String API_VERSION = "10.0.18"; + public static final String API_VERSION = "10.0.21"; public static final String MEKANISM_MODID = "mekanism"; /** * Mekanism debug mode diff --git a/src/api/java/mekanism/api/math/FloatingLong.java b/src/api/java/mekanism/api/math/FloatingLong.java index b11a400e6c4..626dfae26d2 100644 --- a/src/api/java/mekanism/api/math/FloatingLong.java +++ b/src/api/java/mekanism/api/math/FloatingLong.java @@ -37,6 +37,10 @@ public class FloatingLong extends Number implements Comparable { * The value which represents 1.0, this is one more than the value of {@link #MAX_DECIMAL} */ private static final short SINGLE_UNIT = MAX_DECIMAL + 1; + /** + * The maximum value where the decimal can be eliminated without {@link #value} overflowing + */ + private static final long MAX_LONG_SHIFT = Long.divideUnsigned(-1L, SINGLE_UNIT); /** * A constant holding the value {@code 0} */ @@ -327,6 +331,11 @@ public FloatingLong timesEqual(FloatingLong toMultiply) { public FloatingLong divideEquals(FloatingLong toDivide) { if (toDivide.isZero()) { throw new ArithmeticException("Division by zero"); + } else if (this.isZero()) { + return FloatingLong.ZERO; + } else if (toDivide.decimal == 0) { + //If we are dividing by a whole number, use our more optimized division algorithm + return divideEquals(toDivide.value); } BigDecimal divide = new BigDecimal(toString()).divide(new BigDecimal(toDivide.toString()), DECIMAL_DIGITS, RoundingMode.HALF_EVEN); long value = divide.longValue(); @@ -334,6 +343,110 @@ public FloatingLong divideEquals(FloatingLong toDivide) { return setAndClampValues(value, decimal); } + /** + * Divides this {@link FloatingLong} by the given unsigned long primitive, modifying the current object unless it is a constant in which case it instead returns the + * result in a new object. Rounds to the nearest 0.0001 + * + * @param toDivide The value to divide by represented as an unsigned long. + * + * @return The {@link FloatingLong} representing the value of dividing this {@link FloatingLong} by the given unsigned long. + * + * @throws ArithmeticException if {@code toDivide} is zero. + * @apiNote It is recommended to set this to itself to reduce the chance of accidental calls if calling this on a constant {@link FloatingLong} + *
+ * {@code value = value.divideEquals(toDivide)} + */ + public FloatingLong divideEquals(long toDivide) { + if (toDivide == 0) { + throw new ArithmeticException("Division by zero"); + } else if (this.isZero()) { + return FloatingLong.ZERO; + } + long val = Long.divideUnsigned(this.value, toDivide); + long rem = Long.remainderUnsigned(this.value, toDivide); + + //just need to figure out remainder -> decimal + long dec; + + //okay, now what if rem * SINGLE_UNIT * 10L will overflow? + if (Long.compareUnsigned(rem * 10, MAX_LONG_SHIFT) >= 0) { + //if that'll overflow, then toDivide also has to be big. let's just lose some denominator precision and use that + dec = Long.divideUnsigned(rem, Long.divideUnsigned(toDivide, SINGLE_UNIT * 10L)); //same as multiplying numerator + } else { + dec = Long.divideUnsigned(rem * SINGLE_UNIT * 10L, toDivide); //trivial case + dec += Long.divideUnsigned(this.decimal * 10L, toDivide); //need to account for dividing decimal too in case toDivide < 10k + } + + //usually will expect to round to nearest, so we have to do that here + if (Long.remainderUnsigned(dec, 10) >= 5) { + dec += 10; + } + dec /= 10; + return setAndClampValues(val, (short) dec); + } + + /** + * Divides this {@link FloatingLong} by the given {@link FloatingLong} rounded down to an integer value. This gets clamped at the upper bound of {@link + * Long#MAX_VALUE} rather than overflowing. + * + * @param toDivide The {@link FloatingLong} to divide by. + * + * @return A long representing the value of dividing this {@link FloatingLong} by the given {@link FloatingLong}. + * + * @throws ArithmeticException if {@code toDivide} is zero. + */ + public long divideToLong(FloatingLong toDivide) { + if (toDivide.isZero()) { + throw new ArithmeticException("Division by zero"); + } else if (this.smallerThan(toDivide)) { + // Return early if operation will return < 1 + return 0; + } + if (toDivide.greaterOrEqual(ONE)) { + //If toDivide >=1, then we don't care about this.decimal, so can optimize out accounting for that + if (Long.compareUnsigned(toDivide.value, MAX_LONG_SHIFT) <= 0) { //don't case if *this* is < or > than shift + long div = toDivide.value * MAX_DECIMAL + toDivide.decimal; + return (Long.divideUnsigned(this.value, div) * MAX_DECIMAL) + (this.value % div * MAX_DECIMAL / div); + } + // we already know toDivide is > max_long_shift, and other case is impossible + if (Long.compareUnsigned(toDivide.value, Long.divideUnsigned(-1L, 2) + 1L) >= 0) { + //need to check anyways to avoid overflow on toDivide.value +1, so might as well return early + return 1; + } + long q = Long.divideUnsigned(this.value, toDivide.value); + if (q != Long.divideUnsigned(this.value, toDivide.value + 1)) { + // check if we need to account for toDivide.decimal in this case + if (toDivide.value * q + Long.divideUnsigned(toDivide.decimal * q, MAX_DECIMAL) > this.value) { + // if we do, reduce the result + return q - 1; + } + } + return q; + } + // In this case, we're really multiplying (definitely need to account for decimal as well + if (Long.compareUnsigned(this.value, MAX_LONG_SHIFT) >= 0) { + return (this.value / toDivide.decimal) * MAX_DECIMAL //lose some precision here, have to add modulus + + (this.value % toDivide.decimal) * MAX_DECIMAL / toDivide.decimal + + (long) this.decimal * MAX_DECIMAL / toDivide.decimal; + } + long d = this.value * MAX_DECIMAL; + return d / toDivide.decimal + (long) this.decimal * MAX_DECIMAL / toDivide.decimal; //don't care about modulus since we're returning integers + } + + /** + * Divides this {@link FloatingLong} by the given {@link FloatingLong} rounded down to an integer value. This gets clamped at the upper bound of {@link + * Integer#MAX_VALUE} rather than overflowing. + * + * @param toDivide The {@link FloatingLong} to divide by. + * + * @return An int representing the value of dividing this {@link FloatingLong} by the given {@link FloatingLong}. + * + * @throws ArithmeticException if {@code toDivide} is zero. + */ + public int divideToInt(FloatingLong toDivide) { + return MathUtils.clampToInt(divideToLong(toDivide)); + } + /** * Adds the given {@link FloatingLong} to this {@link FloatingLong} and returns the result in a new object. This gets clamped at the upper bound of {@link * FloatingLong#MAX_VALUE} rather than overflowing. @@ -482,7 +595,7 @@ public FloatingLong divide(FloatingLong toDivide) { * @throws ArithmeticException if {@code toDivide} is zero. */ public FloatingLong divide(long toDivide) { - return divide(FloatingLong.create(toDivide)); + return copy().divideEquals(toDivide); } /** @@ -509,8 +622,8 @@ public FloatingLong divide(double toDivide) { * * @param toDivide The {@link FloatingLong} to divide by. * - * @return The {@link FloatingLong} representing the value of dividing this {@link FloatingLong} by the given {@link FloatingLong}, or {@code 1} if the given {@link - * FloatingLong} is {@code 0}. + * @return A double representing the value of dividing this {@link FloatingLong} by the given {@link FloatingLong}, or {@code 1} if the given {@link FloatingLong} is + * {@code 0}. * * @implNote This caps the returned value at {@code 1} */ diff --git a/src/test/java/mekanism/api/math/FloatingLongPropertyTest.java b/src/test/java/mekanism/api/math/FloatingLongPropertyTest.java index 933cf727c9d..d879f7984e5 100644 --- a/src/test/java/mekanism/api/math/FloatingLongPropertyTest.java +++ b/src/test/java/mekanism/api/math/FloatingLongPropertyTest.java @@ -93,4 +93,27 @@ void testDivision() { return b.isZero() || a.divide(b).equals(divideViaBigDecimal(a, b)); }); } + + @Test + @DisplayName("Test dividing to long works correctly") + void testDivisionToLong() { + theoryForAllPairs().check((v1, d1, v2, d2) -> { + FloatingLong a = FloatingLong.createConst(v1, d1.shortValue()); + FloatingLong b = FloatingLong.createConst(v2, d2.shortValue()); + return b.isZero() || a.divideToLong(b) == a.divide(b).longValue(); + }); + } + + @Test + @DisplayName("Test dividing by long works correctly") + void testDivisionByLong() { + qt().forAll( + longs().all(), + integers().between(0, 9_999), + longs().all() + ).check((v1, d1, b) -> { + FloatingLong a = FloatingLong.createConst(v1, d1.shortValue()); + return b == 0 || a.divide(b).equals(divideViaBigDecimal(a, FloatingLong.create(b))); + }); + } } \ No newline at end of file diff --git a/src/test/java/mekanism/api/math/FloatingLongTest.java b/src/test/java/mekanism/api/math/FloatingLongTest.java index e7fe219c66d..13677fe454a 100644 --- a/src/test/java/mekanism/api/math/FloatingLongTest.java +++ b/src/test/java/mekanism/api/math/FloatingLongTest.java @@ -78,6 +78,22 @@ void testDivisionLargeNumerator() { Assertions.assertEquals(FloatingLong.create((long) 649_657 * 337), a.divide(b)); } + @Test + @DisplayName("Test division with a very large denominator") + void testDivisionLargeDenominator() { + FloatingLong a = FloatingLong.create(922355340224119L); + FloatingLong b = FloatingLong.create(-1L); + Assertions.assertEquals(FloatingLong.create(0L, (short) 1), a.divide(b)); + } + + @Test + @DisplayName("Test division denominator underflow to 0") + void testDivisionSmallDenominator() { + FloatingLong a = FloatingLong.create(0, (short) 1); + long b = 2L; + Assertions.assertEquals(FloatingLong.create(0L, (short) 1), a.divide(b)); + } + @Test @DisplayName("Test to string as two decimals") void testConvertingStringToDecimal() {