diff --git a/README.md b/README.md index 59f02e8..a5572ce 100644 --- a/README.md +++ b/README.md @@ -15,5 +15,5 @@ ua.net.uid uid.tools - 1.0.0 + 1.0.2 diff --git a/src/main/java/ua/net/uid/utils/helpers/NumberHelper.java b/src/main/java/ua/net/uid/utils/helpers/NumberHelper.java index e8224aa..494d691 100644 --- a/src/main/java/ua/net/uid/utils/helpers/NumberHelper.java +++ b/src/main/java/ua/net/uid/utils/helpers/NumberHelper.java @@ -15,8 +15,9 @@ */ package ua.net.uid.utils.helpers; -public class NumberHelper { +import java.text.ParsePosition; +public class NumberHelper { private NumberHelper() { } @@ -38,13 +39,241 @@ return result; } + public static Number smallest(long value) { + if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) return Byte.valueOf((byte)value); + if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) return Short.valueOf((short)value); + if (value >= Integer.MIN_VALUE && value <= Integer.MAX_VALUE) return Integer.valueOf((int)value); + return Long.valueOf(value); + } - /*public static Number parse(CharSequence source, ParsePosition position) { + public static Number parse(CharSequence source, ParsePosition position) { int offset = StringHelper.skipWhitespace(source, position.getIndex()); - if (offset < source.length()) { - + int length = source.length(); + if (offset < length) { + char chr = source.charAt(offset); + if (chr == '0') { + return nextZeroDigit(source, position, offset + 1, length); + } else if (chr > '0' && chr <= '9') { + return nextDigit(chr, source, position, offset + 1, length); + } else if (chr == '.'){ + return nextDot(source, position, offset + 1, length); + } + } position.setErrorIndex(offset); return null; - }*/ + } + + public static Number parse(CharSequence source) { + return parse(source, new ParsePosition(0)); + } + + + + + private static Number nextZeroDigit(CharSequence source, ParsePosition position, int end, int length) { + if (end < source.length()) { + char chr = source.charAt(end); + switch (chr) { + case 'x': case 'X': + return nextHexNumeric(source, position, end, length); + case 'b': case 'B': + return nextBinNumeric(source, position, end, length); + default: + long value = 0; + while (chr >= '0' && chr <= '7') { + value = (value << 3) | (chr - '0'); + ++end; + if (end < length) { + chr = source.charAt(end); + } else { + break; + } + } + position.setIndex(end); + return smallest(value); + } + } + position.setIndex(end); + return Byte.valueOf((byte)0); + } + + private static Number nextHexNumeric(CharSequence source, ParsePosition position, int end, int length) { + boolean real = false; + long main = 0; + int base = 0; + if (++end < length) { + do { + char chr = source.charAt(end); + if (chr >= '0' && chr <= '9') { + main = (main << 4) | (chr - '0'); + } else if (chr >= 'A' && chr <= 'F') { + main = (main << 4) | (chr - 'A' + 10); + } else if (chr >= 'a' && chr <= 'f') { + main = (main << 4) | (chr - 'a' + 10); + } else if (!real && chr == '.') { + real = true; + continue; + } else { + if (chr == 'p' || chr == 'P') { + if (++end == length) { + position.setErrorIndex(end); + return null; + } + int sig = 1; + chr = source.charAt(end); + switch (chr) { + case '-': + sig = -1; + case '+': + if (++end == length) { + position.setErrorIndex(end); + return null; + } + chr = source.charAt(end); + } + if (chr < '0' || chr > '9') { + position.setErrorIndex(end); + return null; + } + int pow = chr - '0'; + while (++end < length) { + chr = source.charAt(end); + if (chr < '0' || chr > '9') break; + pow = (pow * 10) + chr - '0'; + } + base += (sig * pow); + real = true; + } + break; + } + if (real) base -= 4; + } while (++end < length); + position.setIndex(end); + if (real) { + if (main > 0 && (main & -2L) == main) { + do { + main >>>= 1; + ++base; + } while ((main & -2L) == main); + } + return Double.valueOf((double)main * Math.pow(2, base)); + } else { + return smallest(main); + } + } + position.setErrorIndex(end); + return null; + } + + private static Number nextBinNumeric(CharSequence source, ParsePosition position, int end, int length) { + if (++end < length) { + char chr = source.charAt(end++); + if (chr == '0' || chr == '1') { + long value = chr - '0'; + while (end < length) { + chr = source.charAt(end); + if (chr == '0' || chr == '1') { + value = (value << 1) | (chr - '0'); + } else { + break; + } + ++end; + } + position.setIndex(end); + return smallest(value); + } + } + position.setErrorIndex(end); + return null; + } + + private static Number nextDigit(char chr, CharSequence source, ParsePosition position, int end, int length) { + long main = chr - '0'; + boolean real = false; + int base = 0; + while (end < length) { + chr = source.charAt(end); + if (chr >= '0' && chr <= '9') { + main = (main * 10) + (chr - '0'); + ++end; + if (real) --base; + } else if (!real && chr == '.') { + real = true; + ++end; + } else if (chr == 'e' || chr == 'E') { + return nextDecExp(main, base, source, position, end, length); + } else { + break; + } + } + position.setIndex(end); + if (real) { + return Double.valueOf((double)main * Math.pow(10, base)); + } else { + return Byte.valueOf((byte)0); + } + } + + private static Number nextDecExp(long main, int base, CharSequence source, ParsePosition position, int end, int length) { + int sig = 1; + if (++end == length) { + position.setErrorIndex(end); + return null; + } + char chr = source.charAt(end); + switch (chr) { + case '-': + sig = -1; + case '+': + if (++end == length) { + position.setErrorIndex(end); + return null; + } + chr = source.charAt(end); + } + if (chr >= '0' && chr <= '9') { + int pow = chr - '0'; + while (++end < length) { + chr = source.charAt(end); + if (chr >= '0' && chr <= '9') { + pow = (pow * 10) + (chr - '0'); + } else { + break; + } + } + position.setIndex(end); + return Double.valueOf((double)main * Math.pow(10, (sig * pow) + base)); + } else { + ++end; + position.setErrorIndex(end); + return null; + } + } + + private static Number nextDot(CharSequence source, ParsePosition position, int end, int length) { + if (end < length) { + char chr = source.charAt(end); + if (chr >= '0' && chr <= '9') { + long main = chr - '0'; + int base = -1; + while (++end < length) { + chr = source.charAt(end); + if (chr >= '0' && chr <= '9') { + main = (main * 10) + (chr - '0'); + --base; + } else if (chr == 'e' || chr == 'E') { + return nextDecExp(main, base, source, position, end, length); + } else { + break; + } + } + position.setIndex(end); + return Double.valueOf((double)main * Math.pow(10, base)); + } + } + position.setIndex(end); + return null; + } + } diff --git a/src/test/java/ua/net/uid/utils/helpers/NumberHelperTest.java b/src/test/java/ua/net/uid/utils/helpers/NumberHelperTest.java index e68b59e..712e228 100644 --- a/src/test/java/ua/net/uid/utils/helpers/NumberHelperTest.java +++ b/src/test/java/ua/net/uid/utils/helpers/NumberHelperTest.java @@ -4,6 +4,9 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.text.ParsePosition; class NumberHelperTest { private final long LONG_VALUE = 0x12345678a1b2c3d4L; @@ -21,4 +24,216 @@ void testBytesToLong() { assertEquals(LONG_VALUE, NumberHelper.bytesToLong(BYTE_ARRAY)); } + + @Test + void testSmallest() { + Number val = NumberHelper.smallest(0); + assertEquals(Byte.class, val.getClass()); + assertEquals(Byte.valueOf((byte)0), val); + + val = NumberHelper.smallest(Byte.MAX_VALUE); + assertEquals(Byte.class, val.getClass()); + assertEquals(Byte.valueOf(Byte.MAX_VALUE), val); + val = NumberHelper.smallest(Byte.MIN_VALUE); + assertEquals(Byte.class, val.getClass()); + assertEquals(Byte.valueOf(Byte.MIN_VALUE), val); + + val = NumberHelper.smallest(Byte.MAX_VALUE + 1); + assertEquals(Short.class, val.getClass()); + assertEquals(Short.valueOf((short) (Byte.MAX_VALUE + 1)), val); + val = NumberHelper.smallest(Byte.MIN_VALUE - 1); + assertEquals(Short.class, val.getClass()); + assertEquals(Short.valueOf((short) (Byte.MIN_VALUE - 1)), val); + + val = NumberHelper.smallest(Short.MAX_VALUE); + assertEquals(Short.class, val.getClass()); + assertEquals(Short.valueOf(Short.MAX_VALUE), val); + val = NumberHelper.smallest(Short.MIN_VALUE); + assertEquals(Short.class, val.getClass()); + assertEquals(Short.valueOf(Short.MIN_VALUE), val); + + val = NumberHelper.smallest(Short.MAX_VALUE + 1); + assertEquals(Integer.class, val.getClass()); + assertEquals(Integer.valueOf((int) (Short.MAX_VALUE + 1)), val); + val = NumberHelper.smallest(Short.MIN_VALUE - 1); + assertEquals(Integer.class, val.getClass()); + assertEquals(Integer.valueOf((int) (Short.MIN_VALUE - 1)), val); + + val = NumberHelper.smallest(Integer.MAX_VALUE); + assertEquals(Integer.class, val.getClass()); + assertEquals(Integer.valueOf(Integer.MAX_VALUE), val); + val = NumberHelper.smallest(Integer.MIN_VALUE); + assertEquals(Integer.class, val.getClass()); + assertEquals(Integer.valueOf(Integer.MIN_VALUE), val); + + val = NumberHelper.smallest((long)Integer.MAX_VALUE + 1); + assertEquals(Long.class, val.getClass()); + assertEquals(Long.valueOf((long) Integer.MAX_VALUE + 1), val); + val = NumberHelper.smallest((long)Integer.MIN_VALUE - 1); + assertEquals(Long.class, val.getClass()); + assertEquals(Long.valueOf((long) Integer.MIN_VALUE - 1), val); + + val = NumberHelper.smallest(Long.MAX_VALUE); + assertEquals(Long.class, val.getClass()); + assertEquals(Long.valueOf(Long.MAX_VALUE), val); + val = NumberHelper.smallest(Long.MIN_VALUE); + assertEquals(Long.class, val.getClass()); + assertEquals(Long.valueOf(Long.MIN_VALUE), val); + } + + @Test + void testParseHexDigit() { + ParsePosition position = new ParsePosition(0); + String source = + "0 0x0 0x7a 0x1f9c 0x00000ffffff 0x10000ffffff 0x0. 0x7.a 0x1f.9c 0x00000.ffffff 0x10000.ffffff "+ + "0x0p0 0x1000P+256 0x1000P-256 0x1.fffffffffffffp1023 0x0.0000000000001P-1022 0x1.0P-1074 0x1.8p+1"; + + assertEquals((byte)0, NumberHelper.parse(source, position)); + assertEquals(1, position.getIndex()); + + assertEquals((byte)0, NumberHelper.parse(source, position)); + assertEquals(5, position.getIndex()); + + assertEquals((byte)0x7a, NumberHelper.parse(source, position)); + assertEquals(10, position.getIndex()); + + assertEquals((short)0x1f9c, NumberHelper.parse(source, position)); + assertEquals(17, position.getIndex()); + + assertEquals(0xffffff, NumberHelper.parse(source, position)); + assertEquals(31, position.getIndex()); + + assertEquals(0x10000ffffffL, NumberHelper.parse(source, position)); + assertEquals(45, position.getIndex()); + + assertEquals(0., NumberHelper.parse(source, position)); + assertEquals(50, position.getIndex()); + + assertEquals(0x7.ap0, NumberHelper.parse(source, position)); + assertEquals(56, position.getIndex()); + + assertEquals(0x1f.9cp0, NumberHelper.parse(source, position)); + assertEquals(64, position.getIndex()); + + assertEquals(0x0.ffffffp0, NumberHelper.parse(source, position)); + assertEquals(79, position.getIndex()); + + assertEquals(0x10000.ffffffp0, NumberHelper.parse(source, position)); + assertEquals(94, position.getIndex()); + + assertEquals(0x0p0, NumberHelper.parse(source, position)); + assertEquals(100, position.getIndex()); + + assertEquals(0x1000p256, NumberHelper.parse(source, position)); + assertEquals(112, position.getIndex()); + + assertEquals(0x1000p-256, NumberHelper.parse(source, position)); + assertEquals(124, position.getIndex()); + + assertEquals(0x1.fffffffffffffp1023, NumberHelper.parse(source, position)); + assertEquals(147, position.getIndex()); + + assertEquals(0x0.0000000000001P-1022, NumberHelper.parse(source, position)); + assertEquals(171, position.getIndex()); + + assertEquals(0x1P-1074, NumberHelper.parse(source, position)); + assertEquals(183, position.getIndex()); + + assertEquals(3., NumberHelper.parse(source, position)); + assertEquals(192, position.getIndex()); + + assertNull(NumberHelper.parse(source, position)); + assertEquals(192, position.getIndex()); + assertEquals(192, position.getErrorIndex()); + } + + @Test + public void testParseOctDigits() { + ParsePosition position = new ParsePosition(0); + String source = "0 01 077 06666 01234567 0123456701234567"; + + assertEquals((byte)0, NumberHelper.parse(source, position)); + assertEquals(1, position.getIndex()); + + assertEquals((byte)01, NumberHelper.parse(source, position)); + assertEquals(4, position.getIndex()); + + assertEquals((byte)077, NumberHelper.parse(source, position)); + assertEquals(8, position.getIndex()); + + assertEquals((short)0_66_66, NumberHelper.parse(source, position)); + assertEquals(14, position.getIndex()); + + assertEquals(01234567, NumberHelper.parse(source, position)); + assertEquals(23, position.getIndex()); + + assertEquals(0123456701234567L, NumberHelper.parse(source, position)); + assertEquals(40, position.getIndex()); + + assertNull(NumberHelper.parse(source, position)); + assertEquals(40, position.getIndex()); + assertEquals(40, position.getErrorIndex()); + } + @Test + public void testParseBinDigits() { + ParsePosition position = new ParsePosition(0); + String source = "0b0 0b1 0b11111111 0b1111111111111111111111111111111111111111111111111111111111111111 0ba"; + + assertEquals((byte)0, NumberHelper.parse(source, position)); + assertEquals(3, position.getIndex()); + + assertEquals((byte)1, NumberHelper.parse(source, position)); + assertEquals(7, position.getIndex()); + + assertEquals((short)0b11111111, NumberHelper.parse(source, position)); + assertEquals(18, position.getIndex()); + + assertEquals((byte)-1, NumberHelper.parse(source, position)); + assertEquals(85, position.getIndex()); + + assertNull(NumberHelper.parse(source, position)); + assertEquals(85, position.getIndex()); + assertEquals(89, position.getErrorIndex()); + } + + + @Test + public void testParseDigits() { + ParsePosition position = new ParsePosition(0); + String source = "1 333 70000 465729124123444 .01 77. 3.333 .1e-3 222.e76 123.456e-78 888e+8 "; + + assertEquals((byte)1, NumberHelper.parse(source, position)); + assertEquals(7, position.getIndex()); + + assertEquals((short)333, NumberHelper.parse(source, position)); + assertEquals(7, position.getIndex()); + + assertEquals(70000, NumberHelper.parse(source, position)); + assertEquals(7, position.getIndex()); + + assertEquals(465729124123444l, NumberHelper.parse(source, position)); + assertEquals(7, position.getIndex()); + + assertEquals(.01, NumberHelper.parse(source, position)); + assertEquals(7, position.getIndex()); + + assertEquals(77., NumberHelper.parse(source, position)); + assertEquals(7, position.getIndex()); + + assertEquals(3.333, NumberHelper.parse(source, position)); + assertEquals(7, position.getIndex()); + + assertEquals(.1e-3, NumberHelper.parse(source, position)); + assertEquals(7, position.getIndex()); + + assertEquals(222.e76, NumberHelper.parse(source, position)); + assertEquals(7, position.getIndex()); + + assertEquals(123.456e-78, NumberHelper.parse(source, position)); + assertEquals(7, position.getIndex()); + + assertEquals(888e+8, NumberHelper.parse(source, position)); + assertEquals(7, position.getIndex()); + } + }