From c0c639229189871cc154aba49f5ce3c9229b588e Mon Sep 17 00:00:00 2001 From: Maxim Date: Thu, 27 Apr 2023 16:37:50 +0200 Subject: [PATCH] v0.0.7 Base32 Base58 bug fix Math utils extension --- README.md | 9 +- pom.xml | 2 +- .../hijackermax/utils/encoders/Base122.java | 35 +++--- .../hijackermax/utils/encoders/Base32.java | 114 ++++++++++++++++++ .../hijackermax/utils/encoders/Base58.java | 8 +- .../hijackermax/utils/encoders/Base85.java | 12 +- .../com/hijackermax/utils/lang/MathUtils.java | 91 ++++++++++++++ .../hijackermax/utils/lang/StringUtils.java | 11 ++ .../encoders/AbstractEncoderTest.java | 13 ++ .../com/hijackermax/encoders/Base122Test.java | 11 ++ .../com/hijackermax/encoders/Base32Test.java | 29 +++++ .../com/hijackermax/encoders/Base58Test.java | 11 ++ .../com/hijackermax/encoders/Base85Test.java | 11 ++ .../com/hijackermax/utils/MathUtilsTest.java | 57 +++++++++ 14 files changed, 385 insertions(+), 29 deletions(-) create mode 100644 src/main/java/com/hijackermax/utils/encoders/Base32.java create mode 100644 src/test/java/com/hijackermax/encoders/Base32Test.java diff --git a/README.md b/README.md index 1037d27..96e0606 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,11 @@ Licensed under the Apache 2.0 License #### Encoders / decoders +##### Base32 + +* Binary-to-text encoding. Based on charset [variant](https://www.crockford.com/base32.html) of Douglas Crockford, + without check + ##### Base58 * Binary-to-text encoding. Character set includes all uppercase and lowercase letters except for "0", "O", "I", and "l" @@ -82,7 +87,7 @@ Just add this to your **pom.xml** com.hijackermax - utils - 0.0.6 + utils + 0.0.7 ``` \ No newline at end of file diff --git a/pom.xml b/pom.xml index 1fea3dd..486e352 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.hijackermax utils - 0.0.6 + 0.0.7 utils A set of utils that can help in app development diff --git a/src/main/java/com/hijackermax/utils/encoders/Base122.java b/src/main/java/com/hijackermax/utils/encoders/Base122.java index 969d63c..2ee6dc0 100644 --- a/src/main/java/com/hijackermax/utils/encoders/Base122.java +++ b/src/main/java/com/hijackermax/utils/encoders/Base122.java @@ -9,8 +9,11 @@ import java.util.Objects; import java.util.function.Consumer; +import static com.hijackermax.utils.lang.MathUtils.add; +import static com.hijackermax.utils.lang.MathUtils.subtract; + /** - * Binary-to-text encoding that is more space-efficient than base64 + * Binary-to-text encoding that is more space-efficient than base64, based on idea of Kevin Albertson * * @since 0.0.6 */ @@ -61,20 +64,20 @@ public static byte[] decode(String source) { if (StringUtils.isBlank(source)) { return new byte[0]; } - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(calculateDecodedBufferSize(source.length())); - Tuple dataPointers = new Tuple<>((byte) 0, (byte) 0); + ByteArrayOutputStream result = new ByteArrayOutputStream(calculateDecodedBufferSize(source.length())); + Tuple dataPointers = new Tuple<>(0, 0); for (char nextChar : source.toCharArray()) { if (nextChar > 0x7F) { int illegalIndex = (nextChar >>> 8) & 0x07; if (illegalIndex != K_SHORTENED) { - pushPartition(INVALID_CHARS[illegalIndex] << 1, dataPointers, outputStream::write); + pushPartition(INVALID_CHARS[illegalIndex] << 1, dataPointers, result::write); } - pushPartition((nextChar & 0x7F) << 1, dataPointers, outputStream::write); + pushPartition((nextChar & 0x7F) << 1, dataPointers, result::write); } else { - pushPartition(nextChar << 1, dataPointers, outputStream::write); + pushPartition(nextChar << 1, dataPointers, result::write); } } - return outputStream.toByteArray(); + return result.toByteArray(); } private static byte nextPartition(byte[] data, int length, Tuple dataPointers) { @@ -83,12 +86,12 @@ private static byte nextPartition(byte[] data, int length, Tuple>> dataPointers.getValue()) & (data[dataPointers.getKey()] & 0xFF)) << dataPointers.getValue()) >>> 1; - dataPointers.modifyValue(v -> v + 7); + dataPointers.modifyValue(add(7)); if (dataPointers.getValue() < 8) { return (byte) firstPart; } - dataPointers.modifyValue(v -> v - 8); - dataPointers.modifyKey(k -> k + 1); + dataPointers.modifyValue(subtract(8)); + dataPointers.modifyKey(add(1)); if (dataPointers.getKey() >= length) { return (byte) firstPart; } @@ -97,13 +100,13 @@ private static byte nextPartition(byte[] data, int length, Tuple dataPointers, Consumer valueConsumer) { - dataPointers.modifyKey(k -> (byte) (k | (nextValue >>> dataPointers.getValue()))); - dataPointers.modifyValue(v -> (byte) (v + 7)); + private static void pushPartition(int nextValue, Tuple dataPointers, Consumer valueConsumer) { + dataPointers.modifyKey(k -> k | (nextValue >>> dataPointers.getValue())); + dataPointers.modifyValue(add(7)); if (dataPointers.getValue() >= 8) { - valueConsumer.accept(dataPointers.getKey()); - dataPointers.modifyValue(v -> (byte) (v - 8)); - dataPointers.setKey((byte) (nextValue << (7 - dataPointers.getValue()) & 0xFF)); + valueConsumer.accept((byte) (dataPointers.getKey() & 0xFF)); + dataPointers.modifyValue(subtract(8)); + dataPointers.setKey(nextValue << (7 - dataPointers.getValue()) & 0xFF); } } diff --git a/src/main/java/com/hijackermax/utils/encoders/Base32.java b/src/main/java/com/hijackermax/utils/encoders/Base32.java new file mode 100644 index 0000000..958be2c --- /dev/null +++ b/src/main/java/com/hijackermax/utils/encoders/Base32.java @@ -0,0 +1,114 @@ +package com.hijackermax.utils.encoders; + +import com.hijackermax.utils.entities.Tuple; +import com.hijackermax.utils.lang.ObjectUtils; +import com.hijackermax.utils.lang.StringUtils; + +import java.io.ByteArrayOutputStream; +import java.util.Arrays; +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; + +import static com.hijackermax.utils.lang.MathUtils.add; +import static com.hijackermax.utils.lang.MathUtils.subtract; + +/** + * Binary-to-text encoding based on variant of Douglas Crockford, without check + * + * @since 0.0.7 + */ +public final class Base32 { + private static final char[] CHARSET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ".toCharArray(); + private static final byte[] IGNORED_CHARS = {0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x20, 0x2D}; + private static final Map ALIASES = Map.of('O', 0, 'I', 1, 'L', 1); + private static final byte STOP_BYTE = (byte) 0x5B; + + private Base32() { + } + + /** + * Encodes provided byte array to Base32 String {@link String} + * + * @param source byte array + * @return encoded {@link String} + * @since 0.0.7 + */ + public static String encode(byte[] source) { + int dataLength = Objects.requireNonNull(source).length; + StringBuilder result = new StringBuilder(calculateEncodeBufferSize(dataLength)); + Tuple pointers = new Tuple<>(0, 0); + for (byte partition = nextPartition(source, pointers); STOP_BYTE != partition; partition = nextPartition(source, pointers)) { + result.append(CHARSET[partition]); + } + return result.toString(); + } + + + /** + * Decodes provided {@link String} to byte array + * + * @param source {@link String} + * @return decoded byte array + * @throws IllegalArgumentException if provided string is not base32 encoded + * @since 0.0.7 + */ + public static byte[] decode(String source) { + if (StringUtils.isBlank(source)) { + return new byte[0]; + } + ByteArrayOutputStream result = new ByteArrayOutputStream(calculateDecodedBufferSize(source.length())); + Tuple dataHolder = new Tuple<>(0, 0); + for (char nextChar : source.toCharArray()) { + if (0 <= Arrays.binarySearch(IGNORED_CHARS, (byte) nextChar)) { + continue; + } + char upperCaseChar = Character.toUpperCase(nextChar); + int idx = Arrays.binarySearch(CHARSET, upperCaseChar); + int partition = idx >= 0 ? idx : ObjectUtils.valueOrDefault(ALIASES.get(upperCaseChar), -1); + if (0 > partition) { + throw new IllegalArgumentException("Provided source string is not Base32 encoded"); + } + pushPartition(partition << 3, dataHolder, result::write); + } + return result.toByteArray(); + } + + private static byte nextPartition(byte[] data, Tuple pointers) { + if (pointers.getKey() >= data.length) { + return STOP_BYTE; + } + int firstPart = (((0xFE >>> pointers.getValue()) & (data[pointers.getKey()] & 0xFF)) + << pointers.getValue()) >>> 3; + pointers.modifyValue(add(5)); + if (pointers.getValue() < 8) { + return (byte) firstPart; + } + pointers.modifyValue(subtract(8)); + pointers.modifyKey(add(1)); + if (pointers.getKey() >= data.length) { + return (byte) firstPart; + } + int secondPart = (((0xFF00 >>> pointers.getValue()) & (data[pointers.getKey()] & 0xFF)) & 0xFF) + >>> 8 - pointers.getValue(); + return (byte) (firstPart | secondPart); + } + + private static void pushPartition(int nextValue, Tuple dataHolder, Consumer valueConsumer) { + dataHolder.modifyValue(k -> k | (nextValue >> dataHolder.getKey())); + dataHolder.modifyKey(add(5)); + if (dataHolder.getKey() >= 8) { + valueConsumer.accept((byte) (dataHolder.getValue() & 0xFF)); + dataHolder.modifyKey(subtract(8)); + dataHolder.setValue(nextValue << (5 - dataHolder.getKey()) & 0xFF); + } + } + + private static int calculateEncodeBufferSize(int byteArrayLength) { + return ((byteArrayLength * 8) / 5) + 7; + } + + private static int calculateDecodedBufferSize(int stringLength) { + return (stringLength * 5) / 8; + } +} diff --git a/src/main/java/com/hijackermax/utils/encoders/Base58.java b/src/main/java/com/hijackermax/utils/encoders/Base58.java index 1345c65..3c92396 100644 --- a/src/main/java/com/hijackermax/utils/encoders/Base58.java +++ b/src/main/java/com/hijackermax/utils/encoders/Base58.java @@ -46,7 +46,7 @@ public static String encode(byte[] source) { if (isLeadingZero) { boolean isByteZero = 0 == nextByte; if (isByteZero) { - result.insert(0, (char) 0x49); + result.insert(0, (char) 0x31); } isLeadingZero = isByteZero; } @@ -77,7 +77,7 @@ public static byte[] decode(String source) { throw new IllegalArgumentException("Provided source string is not Base58 encoded"); } if (isLeadingOne) { - boolean isCharOne = 0x49 == nextChar; + boolean isCharOne = 0x31 == nextChar; if (isCharOne) { result.write(0x00); } @@ -92,10 +92,10 @@ public static byte[] decode(String source) { } private static int calculateEncodeBufferSize(int byteArrayLength) { - return byteArrayLength * 7 / 5; + return byteArrayLength * 8 / 6; } private static int calculateDecodedBufferSize(int stringLength) { - return stringLength * 5 / 7; + return stringLength * 6 / 8; } } diff --git a/src/main/java/com/hijackermax/utils/encoders/Base85.java b/src/main/java/com/hijackermax/utils/encoders/Base85.java index 46b7efa..78423c6 100644 --- a/src/main/java/com/hijackermax/utils/encoders/Base85.java +++ b/src/main/java/com/hijackermax/utils/encoders/Base85.java @@ -13,9 +13,9 @@ * @since 0.0.6 */ public final class Base85 { - private static final int ASCII_START_IDX = 0x21; + private static final int CHAR_START_IDX = 0x21; private static final int[] POWERS_85 = {52200625, 614125, 7225, 85, 1}; - private static final byte[] IGNORED_ASCII = {0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x20}; + private static final byte[] IGNORED_CHARS = {0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x20}; private Base85() { } @@ -58,7 +58,7 @@ public static byte[] decode(String source) { int paginationIdx = 0; for (int idx = 0; idx < sourceBytes.length; idx++) { byte nextByte = sourceBytes[idx]; - if (0 <= Arrays.binarySearch(IGNORED_ASCII, nextByte)) { + if (0 <= Arrays.binarySearch(IGNORED_CHARS, nextByte)) { continue; } boolean isZeroPlaceholder = 0x7A == nextByte; @@ -69,7 +69,7 @@ public static byte[] decode(String source) { } if (isZeroPlaceholder) { while (paginationIdx < 5) { - pagination[paginationIdx++] = ASCII_START_IDX; + pagination[paginationIdx++] = CHAR_START_IDX; } } else { pagination[paginationIdx++] = nextByte; @@ -102,7 +102,7 @@ private static byte[] encodePartition(byte[] pagination, int actualBytes) { int encodedSize = actualBytes + 1; byte[] encodedPartition = new byte[encodedSize]; for (int idx = 0; idx < encodedSize; idx++) { - encodedPartition[idx] = (byte) ((unsignedInt / POWERS_85[idx]) + ASCII_START_IDX); + encodedPartition[idx] = (byte) ((unsignedInt / POWERS_85[idx]) + CHAR_START_IDX); unsignedInt %= POWERS_85[idx]; } return encodedPartition; @@ -111,7 +111,7 @@ private static byte[] encodePartition(byte[] pagination, int actualBytes) { private static byte[] decodePartition(byte[] partition) { int value = 0; for (int idx = 0; idx < 5; idx++) { - value += (partition[idx] - ASCII_START_IDX) * POWERS_85[idx]; + value += (partition[idx] - CHAR_START_IDX) * POWERS_85[idx]; } return new byte[]{(byte) (value >>> 24), (byte) (value >>> 16), (byte) (value >>> 8), (byte) value}; } diff --git a/src/main/java/com/hijackermax/utils/lang/MathUtils.java b/src/main/java/com/hijackermax/utils/lang/MathUtils.java index 2a08bca..c3d1c7d 100644 --- a/src/main/java/com/hijackermax/utils/lang/MathUtils.java +++ b/src/main/java/com/hijackermax/utils/lang/MathUtils.java @@ -1,5 +1,8 @@ package com.hijackermax.utils.lang; +import java.util.function.Function; +import java.util.function.UnaryOperator; + /** * Set of utility methods that can help with math operations */ @@ -233,4 +236,92 @@ public static double absMultiply(double a, double b) { public static float absMultiply(float a, float b) { return Math.abs(a * b); } + + /** + * Provides {@link UnaryOperator} which subtracts provided value from {@link Function} argument + * + * @param b subtrahend operand + * @return {@link UnaryOperator} which subtracts provided value from {@link Function} argument + * @since 0.0.7 + */ + public static UnaryOperator subtract(int b) { + return a -> subtract(a, b); + } + + /** + * Provides {@link UnaryOperator} which subtracts provided value from {@link Function} argument + * + * @param b subtrahend operand + * @return {@link UnaryOperator} which subtracts provided value from {@link Function} argument + * @since 0.0.7 + */ + public static UnaryOperator subtract(long b) { + return a -> subtract(a, b); + } + + /** + * Provides {@link UnaryOperator} which subtracts provided value from {@link Function} argument + * + * @param b subtrahend operand + * @return {@link UnaryOperator} which subtracts provided value from {@link Function} argument + * @since 0.0.7 + */ + public static UnaryOperator subtract(float b) { + return a -> subtract(a, b); + } + + /** + * Provides {@link UnaryOperator} which subtracts provided value from {@link Function} argument + * + * @param b subtrahend operand + * @return {@link UnaryOperator} which subtracts provided value from {@link Function} argument + * @since 0.0.7 + */ + public static UnaryOperator subtract(double b) { + return a -> subtract(a, b); + } + + /** + * Provides {@link UnaryOperator} which adds provided value to {@link Function} argument + * + * @param b addend + * @return {@link UnaryOperator} which adds provided value to {@link Function} argument + * @since 0.0.7 + */ + public static UnaryOperator add(int b) { + return a -> Integer.sum(a, b); + } + + /** + * Provides {@link UnaryOperator} which adds provided value to {@link Function} argument + * + * @param b addend + * @return {@link UnaryOperator} which adds provided value to {@link Function} argument + * @since 0.0.7 + */ + public static UnaryOperator add(long b) { + return a -> Long.sum(a, b); + } + + /** + * Provides {@link UnaryOperator} which adds provided value to {@link Function} argument + * + * @param b addend + * @return {@link UnaryOperator} which adds provided value to {@link Function} argument + * @since 0.0.7 + */ + public static UnaryOperator add(float b) { + return a -> Float.sum(a, b); + } + + /** + * Provides {@link UnaryOperator} which adds provided value to {@link Function} argument + * + * @param b addend + * @return {@link UnaryOperator} which adds provided value to {@link Function} argument + * @since 0.0.7 + */ + public static UnaryOperator add(double b) { + return a -> Double.sum(a, b); + } } diff --git a/src/main/java/com/hijackermax/utils/lang/StringUtils.java b/src/main/java/com/hijackermax/utils/lang/StringUtils.java index fe49e8b..87f1a1d 100644 --- a/src/main/java/com/hijackermax/utils/lang/StringUtils.java +++ b/src/main/java/com/hijackermax/utils/lang/StringUtils.java @@ -45,6 +45,17 @@ public final class StringUtils { * Underscore {@link String} constant */ public static final String UNDERSCORE = "_"; + + /** + * Zero {@link String} constant + */ + public static final String ZERO = "0"; + + /** + * One {@link String} constant + */ + public static final String ONE = "1"; + private static final Pattern WHITESPACES_PATTERN = Pattern.compile("\\s+"); private static final Pattern NON_DIGITS_PATTERN = Pattern.compile("[^\\d.]"); diff --git a/src/test/java/com/hijackermax/encoders/AbstractEncoderTest.java b/src/test/java/com/hijackermax/encoders/AbstractEncoderTest.java index 026ba6c..0f3e2e8 100644 --- a/src/test/java/com/hijackermax/encoders/AbstractEncoderTest.java +++ b/src/test/java/com/hijackermax/encoders/AbstractEncoderTest.java @@ -43,8 +43,21 @@ private void runVariableLengthEncodeDecode(int length) { assertEquals(source, decoded, String.format("Source and decode don't match, source length %d", length)); } + @Test + void testEncodeDecodeStatic() { + byte[] sourceBytes = getSourceForEncode(); + String encoded = getEncoder().apply(sourceBytes); + assertFalse(encoded.isEmpty()); + assertEquals(getEncodedSource(), encoded); + byte[] decodedBytes = getDecoder().apply(encoded); + assertArrayEquals(sourceBytes, decodedBytes); + } abstract Function getEncoder(); abstract Function getDecoder(); + + abstract byte[] getSourceForEncode(); + + abstract String getEncodedSource(); } diff --git a/src/test/java/com/hijackermax/encoders/Base122Test.java b/src/test/java/com/hijackermax/encoders/Base122Test.java index f449529..432407d 100644 --- a/src/test/java/com/hijackermax/encoders/Base122Test.java +++ b/src/test/java/com/hijackermax/encoders/Base122Test.java @@ -2,6 +2,7 @@ import com.hijackermax.utils.encoders.Base122; +import java.nio.charset.StandardCharsets; import java.util.function.Function; class Base122Test extends AbstractEncoderTest { @@ -14,4 +15,14 @@ Function getEncoder() { Function getDecoder() { return Base122::decode; } + + @Override + byte[] getSourceForEncode() { + return "TestString124567890FooBar!@#$%^&*()[]".getBytes(StandardCharsets.UTF_8); + } + + @Override + String getEncodedSource() { + return "*\u0019.7ύhr4[Ls\tHh5\u001B˧\u0003IA\fo7PL\u0017\u0011\u0005£\u0012\t+b1(P)-W "; + } } diff --git a/src/test/java/com/hijackermax/encoders/Base32Test.java b/src/test/java/com/hijackermax/encoders/Base32Test.java new file mode 100644 index 0000000..5ee6e1f --- /dev/null +++ b/src/test/java/com/hijackermax/encoders/Base32Test.java @@ -0,0 +1,29 @@ +package com.hijackermax.encoders; + +import com.hijackermax.utils.encoders.Base32; + +import java.nio.charset.StandardCharsets; +import java.util.function.Function; + +class Base32Test extends AbstractEncoderTest { + + @Override + Function getEncoder() { + return Base32::encode; + } + + @Override + Function getDecoder() { + return Base32::decode; + } + + @Override + byte[] getSourceForEncode() { + return "TestString124567890FooBar!@#$%^&*()[]".getBytes(StandardCharsets.UTF_8); + } + + @Override + String getEncodedSource() { + return "AHJQ6X2KEHS6JVK764S38D9P6WW3JC26DXQM4RBJ45026915BRK2MA19BDEG"; + } +} diff --git a/src/test/java/com/hijackermax/encoders/Base58Test.java b/src/test/java/com/hijackermax/encoders/Base58Test.java index 4b45407..2a6c5fd 100644 --- a/src/test/java/com/hijackermax/encoders/Base58Test.java +++ b/src/test/java/com/hijackermax/encoders/Base58Test.java @@ -2,6 +2,7 @@ import com.hijackermax.utils.encoders.Base58; +import java.nio.charset.StandardCharsets; import java.util.function.Function; class Base58Test extends AbstractEncoderTest { @@ -15,4 +16,14 @@ Function getEncoder() { Function getDecoder() { return Base58::decode; } + + @Override + byte[] getSourceForEncode() { + return "TestString124567890FooBar!@#$%^&*()[]".getBytes(StandardCharsets.UTF_8); + } + + @Override + String getEncodedSource() { + return "3q4DFPT8Jw8FG6jg9qdEpSaCvNBpejFY68kA6y6aacU2j8z18ig"; + } } diff --git a/src/test/java/com/hijackermax/encoders/Base85Test.java b/src/test/java/com/hijackermax/encoders/Base85Test.java index 1aee893..483c407 100644 --- a/src/test/java/com/hijackermax/encoders/Base85Test.java +++ b/src/test/java/com/hijackermax/encoders/Base85Test.java @@ -2,6 +2,7 @@ import com.hijackermax.utils.encoders.Base85; +import java.nio.charset.StandardCharsets; import java.util.function.Function; class Base85Test extends AbstractEncoderTest { @@ -14,4 +15,14 @@ Function getEncoder() { Function getDecoder() { return Base85::decode; } + + @Override + byte[] getSourceForEncode() { + return "TestString124567890FooBar!@#$%^&*()[]".getBytes(StandardCharsets.UTF_8); + } + + @Override + String getEncodedSource() { + return "<+U,m;fm%oDJ([Z1c70M3&rZ^Df7sNEZm[m,UHbD.OZ`M>l"; + } } diff --git a/src/test/java/com/hijackermax/utils/MathUtilsTest.java b/src/test/java/com/hijackermax/utils/MathUtilsTest.java index de0c307..8798447 100644 --- a/src/test/java/com/hijackermax/utils/MathUtilsTest.java +++ b/src/test/java/com/hijackermax/utils/MathUtilsTest.java @@ -135,4 +135,61 @@ void testAbsMultiplyDouble() { assertEquals(0D, MathUtils.absMultiply(20D, -0D)); assertEquals(100D, MathUtils.absMultiply(10D, -10D)); } + + @Test + void testSubtractLongUnaryOperator() { + assertEquals(-10L, MathUtils.subtract(20L).apply(10L)); + assertEquals(0L, MathUtils.subtract(0L).apply(0L)); + assertEquals(20L, MathUtils.subtract(-10L).apply(10L)); + } + + @Test + void testSubtractIntUnaryOperator() { + assertEquals(-10, MathUtils.subtract(20).apply(10)); + assertEquals(0, MathUtils.subtract(0).apply(0)); + assertEquals(20, MathUtils.subtract(-10).apply(10)); + } + + @Test + void testSubtractFloatUnaryOperator() { + assertEquals(-10F, MathUtils.subtract(20F).apply(10F)); + assertEquals(0F, MathUtils.subtract(0F).apply(0F)); + assertEquals(20F, MathUtils.subtract(-10F).apply(10F)); + } + + @Test + void testSubtractDoubleUnaryOperator() { + assertEquals(-10D, MathUtils.subtract(20D).apply(10D)); + assertEquals(0D, MathUtils.subtract(0D).apply(0D)); + assertEquals(20D, MathUtils.subtract(-10D).apply(10D)); + } + + + @Test + void testAddLongUnaryOperator() { + assertEquals(30L, MathUtils.add(20L).apply(10L)); + assertEquals(0L, MathUtils.add(0L).apply(0L)); + assertEquals(0L, MathUtils.add(-10L).apply(10L)); + } + + @Test + void testAddIntUnaryOperator() { + assertEquals(30, MathUtils.add(20).apply(10)); + assertEquals(0, MathUtils.add(0).apply(0)); + assertEquals(0, MathUtils.add(-10).apply(10)); + } + + @Test + void testAddFloatUnaryOperator() { + assertEquals(30F, MathUtils.add(20F).apply(10F)); + assertEquals(0F, MathUtils.add(0F).apply(0F)); + assertEquals(0F, MathUtils.add(-10F).apply(10F)); + } + + @Test + void testAddDoubleUnaryOperator() { + assertEquals(30D, MathUtils.add(20D).apply(10D)); + assertEquals(0D, MathUtils.add(0D).apply(0D)); + assertEquals(0D, MathUtils.add(-10D).apply(10D)); + } }