From 49cef98d9769c6145b66daa057b4ba6428e945d9 Mon Sep 17 00:00:00 2001 From: Andrei Davydov Date: Mon, 11 Sep 2023 19:59:52 +0300 Subject: [PATCH 1/5] Java: Format string in the same way as Double.toString() --- .../java/com/epam/deltix/dfp/JavaImpl.java | 47 +++--- .../com/epam/deltix/dfp/BigDecimalTest.java | 8 +- .../com/epam/deltix/dfp/Decimal64Test.java | 20 +-- .../epam/deltix/dfp/Decimal64UtilsTest.java | 150 +++++++++--------- .../com/epam/deltix/dfp/FromDoubleTest.java | 4 +- .../com/epam/deltix/dfp/JavaImplTest.java | 74 +++++++-- 6 files changed, 185 insertions(+), 118 deletions(-) diff --git a/java/dfp/src/main/java/com/epam/deltix/dfp/JavaImpl.java b/java/dfp/src/main/java/com/epam/deltix/dfp/JavaImpl.java index 5afc265e..1960a625 100644 --- a/java/dfp/src/main/java/com/epam/deltix/dfp/JavaImpl.java +++ b/java/dfp/src/main/java/com/epam/deltix/dfp/JavaImpl.java @@ -420,7 +420,7 @@ public static Appendable appendToRefImpl(final long value, final Appendable appe } if (0 == partsCoefficient) - return appendable.append('0'); + return appendable.append("0.0"); if (value < 0) appendable.append('-'); @@ -432,6 +432,7 @@ public static Appendable appendToRefImpl(final long value, final Appendable appe appendLongTo(partsCoefficient, appendable, digits); for (int i = 0; i < exponent; i += 1) appendable.append('0'); + appendable.append(".0"); } else if (digits + exponent > 0) { final long integralPart = partsCoefficient / POWERS_OF_TEN[-exponent]; final long fractionalPart = partsCoefficient % POWERS_OF_TEN[-exponent]; @@ -441,6 +442,8 @@ public static Appendable appendToRefImpl(final long value, final Appendable appe for (int i = numberOfDigits(fractionalPart); i < -exponent; i += 1) appendable.append('0'); appendLongTo(dropTrailingZeros(fractionalPart), appendable); + } else { + appendable.append(".0"); } } else { appendable.append("0."); @@ -546,16 +549,18 @@ public static String fastToString(final long value) { } if (partsCoefficient == 0) - return "0"; + return "0.0"; int exponent = partsExponent - EXPONENT_BIAS; final char[] buffer = CHAR_BUFFER.get(); if (exponent >= 0) { - int bi = buffer.length - exponent; - for (int i = buffer.length - exponent; i < buffer.length; ++i) - buffer[i] = '0'; + int bi = buffer.length; + buffer[--bi] = '0'; + buffer[--bi] = '.'; + for (int i = 0; i < exponent; ++i) + buffer[--bi] = '0'; while (partsCoefficient > 0) { bi = formatUIntFromBcdTable((int) (partsCoefficient % BCD_DIVIDER), buffer, bi); @@ -608,8 +613,8 @@ public static String fastToString(final long value) { while (buffer[be - 1] == '0') --be; - if (buffer[be - 1] == '.') - --be; + if (buffer[be - 1] == '.' && be < buffer.length) + buffer[be++] = '0'; return new String(buffer, bi, be - bi); @@ -786,16 +791,18 @@ public static StringBuilder fastAppendToStringBuilder(final long value, final St } if (partsCoefficient == 0) - return stringBuilder.append('0'); + return stringBuilder.append("0.0"); int exponent = partsExponent - EXPONENT_BIAS; final char[] buffer = CHAR_BUFFER.get(); if (exponent >= 0) { - int bi = buffer.length - exponent; - for (int i = buffer.length - exponent; i < buffer.length; ++i) - buffer[i] = '0'; + int bi = buffer.length; + buffer[--bi] = '0'; + buffer[--bi] = '.'; + for (int i = 0; i < exponent; ++i) + buffer[--bi] = '0'; while (partsCoefficient > 0) { bi = formatUIntFromBcdTable((int) (partsCoefficient % BCD_DIVIDER), buffer, bi); @@ -848,8 +855,8 @@ public static StringBuilder fastAppendToStringBuilder(final long value, final St while (buffer[be - 1] == '0') --be; - if (buffer[be - 1] == '.') - --be; + if (buffer[be - 1] == '.' && be < buffer.length) + buffer[be++] = '0'; return stringBuilder.append(buffer, bi, be - bi); @@ -1083,7 +1090,7 @@ public static Appendable fastAppendToAppendable(final long value, final Appendab } if (partsCoefficient == 0) - return appendable.append('0'); + return appendable.append("0.0"); int exponent = partsExponent - EXPONENT_BIAS; @@ -1091,9 +1098,11 @@ public static Appendable fastAppendToAppendable(final long value, final Appendab final char[] buffer = charBuffer.buffer; if (exponent >= 0) { - int bi = buffer.length - exponent; - for (int i = buffer.length - exponent; i < buffer.length; ++i) - buffer[i] = '0'; + int bi = buffer.length; + buffer[--bi] = '0'; + buffer[--bi] = '.'; + for (int i = 0; i < exponent; ++i) + buffer[--bi] = '0'; while (partsCoefficient > 0) { bi = formatUIntFromBcdTable((int) (partsCoefficient % BCD_DIVIDER), buffer, bi); @@ -1146,8 +1155,8 @@ public static Appendable fastAppendToAppendable(final long value, final Appendab while (buffer[be - 1] == '0') --be; - if (buffer[be - 1] == '.') - --be; + if (buffer[be - 1] == '.' && be < buffer.length) + buffer[be++] = '0'; return appendable.append(charBuffer.setRange(bi, be - bi)); diff --git a/java/dfp/src/test/java/com/epam/deltix/dfp/BigDecimalTest.java b/java/dfp/src/test/java/com/epam/deltix/dfp/BigDecimalTest.java index 212bb9f1..9b558600 100644 --- a/java/dfp/src/test/java/com/epam/deltix/dfp/BigDecimalTest.java +++ b/java/dfp/src/test/java/com/epam/deltix/dfp/BigDecimalTest.java @@ -40,7 +40,9 @@ public void conversionToBigDecimalAndBack() { } private static void toBigDecimalAndBack(@Decimal final long aD64) { - final String aStr = Decimal64Utils.toString(aD64); + String aStr = Decimal64Utils.toString(aD64); + if (aStr.endsWith(".0")) + aStr = aStr.substring(0, aStr.length() - 2); final BigDecimal big = Decimal64Utils.toBigDecimal(aD64); @@ -94,10 +96,10 @@ private static void toDecimal64(final BigDecimal a) { final String aStr = a.toPlainString(); String bStr = Decimal64Utils.toString(b); - if (!aStr.contains(".") && aStr.length() != bStr.length()) + if (!aStr.contains(".") && (aStr.length() + 2) != bStr.length()) throw new RuntimeException("BigDecimal(=" + a + ") conversion to Decimal64 order mismatch."); - bStr = trimBackZerosAndDot(Decimal64Utils.toString(b)); + bStr = trimBackZerosAndDot(bStr.endsWith(".0") ? bStr.substring(0, bStr.length() - 2) : bStr); if (!bStr.isEmpty()) bStr = bStr.substring(0, bStr.length() - 1); // Remove rounded symbol diff --git a/java/dfp/src/test/java/com/epam/deltix/dfp/Decimal64Test.java b/java/dfp/src/test/java/com/epam/deltix/dfp/Decimal64Test.java index 1849e803..ae2fb549 100644 --- a/java/dfp/src/test/java/com/epam/deltix/dfp/Decimal64Test.java +++ b/java/dfp/src/test/java/com/epam/deltix/dfp/Decimal64Test.java @@ -261,14 +261,14 @@ public void numberConversionTest() { @Test public void toStringTest() { - Assert.assertEquals("0", Decimal64.fromLong(0).toString()); - Assert.assertEquals("42", Decimal64.fromLong(42).toString()); - Assert.assertEquals(String.valueOf(Integer.MAX_VALUE), Decimal64.fromInt(Integer.MAX_VALUE).toString()); - Assert.assertEquals(String.valueOf(Integer.MIN_VALUE), Decimal64.fromInt(Integer.MIN_VALUE).toString()); - Assert.assertEquals(String.valueOf(Integer.MAX_VALUE), Decimal64.fromLong(Integer.MAX_VALUE).toString()); - Assert.assertEquals(String.valueOf(Integer.MIN_VALUE), Decimal64.fromLong(Integer.MIN_VALUE).toString()); - Assert.assertEquals(String.valueOf(Integer.MAX_VALUE), Decimal64.fromFixedPoint(Integer.MAX_VALUE, 0).toString()); - Assert.assertEquals(String.valueOf(Integer.MIN_VALUE), Decimal64.fromFixedPoint(Integer.MIN_VALUE, 0).toString()); + Assert.assertEquals("0.0", Decimal64.fromLong(0).toString()); + Assert.assertEquals("42.0", Decimal64.fromLong(42).toString()); + Assert.assertEquals(Integer.MAX_VALUE + ".0", Decimal64.fromInt(Integer.MAX_VALUE).toString()); + Assert.assertEquals(Integer.MIN_VALUE + ".0", Decimal64.fromInt(Integer.MIN_VALUE).toString()); + Assert.assertEquals(Integer.MAX_VALUE + ".0", Decimal64.fromLong(Integer.MAX_VALUE).toString()); + Assert.assertEquals(Integer.MIN_VALUE + ".0", Decimal64.fromLong(Integer.MIN_VALUE).toString()); + Assert.assertEquals(Integer.MAX_VALUE + ".0", Decimal64.fromFixedPoint(Integer.MAX_VALUE, 0).toString()); + Assert.assertEquals(Integer.MIN_VALUE + ".0", Decimal64.fromFixedPoint(Integer.MIN_VALUE, 0).toString()); Assert.assertEquals("123.456", Decimal64.fromDouble(123.456).toString()); Assert.assertEquals("123.4567", Decimal64.fromFixedPoint(1234567, 4).toString()); @@ -282,8 +282,8 @@ public void toStringTest() { Assert.assertEquals("Infinity", Decimal64.toString(Decimal64.POSITIVE_INFINITY)); Assert.assertEquals("-Infinity", Decimal64.toString(Decimal64.NEGATIVE_INFINITY)); - Assert.assertEquals("0", Decimal64.toString(Decimal64.ZERO)); - Assert.assertEquals("1000000", Decimal64.toString(Decimal64.MILLION)); + Assert.assertEquals("0.0", Decimal64.toString(Decimal64.ZERO)); + Assert.assertEquals("1000000.0", Decimal64.toString(Decimal64.MILLION)); Assert.assertEquals("0.01", Decimal64.toString(Decimal64.ONE_HUNDREDTH)); } } diff --git a/java/dfp/src/test/java/com/epam/deltix/dfp/Decimal64UtilsTest.java b/java/dfp/src/test/java/com/epam/deltix/dfp/Decimal64UtilsTest.java index 1cd8e8a9..e141f9f0 100644 --- a/java/dfp/src/test/java/com/epam/deltix/dfp/Decimal64UtilsTest.java +++ b/java/dfp/src/test/java/com/epam/deltix/dfp/Decimal64UtilsTest.java @@ -414,15 +414,15 @@ public Long apply(Long aLong) { }, a, b, message); } }, - "-1", "-1.0", - "0", "-0.7", - "0", "-0.5", - "0", "-0.2", - "0", "0.0", - "1", "0.2", - "1", "0.5", - "1", "0.7", - "1", "1.0" + "-1.0", "-1.0", + "0.0", "-0.7", + "0.0", "-0.5", + "0.0", "-0.2", + "0.0", "0.0", + "1.0", "0.2", + "1.0", "0.5", + "1.0", "0.7", + "1.0", "1.0" ); @Decimal final long multiple = Decimal64Utils.parse("0.1"); @@ -441,17 +441,17 @@ public Long apply(Long x) { } }, "-0.1", "-0.10", - "0", "-0.07", - "0", "-0.05", - "0", "-0.02", - "0", "-0.02", - "0", "0.0", + "0.0", "-0.07", + "0.0", "-0.05", + "0.0", "-0.02", + "0.0", "-0.02", + "0.0", "0.0", "0.1", "0.02", "0.1", "0.05", "0.1", "0.07", "0.1", "0.10", - "1000", "999.9001", - "-1000", "-1000.0999" + "1000.0", "999.9001", + "-1000.0", "-1000.0999" ); } @@ -472,16 +472,16 @@ public Long apply(Long aLong) { }, a, b, message); } }, - "-1", "-1.0", - "-1", "-1.0", - "-1", "-0.7", - "-1", "-0.5", - "-1", "-0.2", - "0", "0.0", - "0", "0.2", - "0", "0.5", - "0", "0.7", - "1", "1.0" + "-1.0", "-1.0", + "-1.0", "-1.0", + "-1.0", "-0.7", + "-1.0", "-0.5", + "-1.0", "-0.2", + "0.0", "0.0", + "0.0", "0.2", + "0.0", "0.5", + "0.0", "0.7", + "1.0", "1.0" ); @Decimal final long multiple = Decimal64Utils.parse("0.1"); @@ -504,13 +504,13 @@ public Long apply(Long x) { "-0.1", "-0.05", "-0.1", "-0.02", "-0.1", "-0.02", - "0", "0.0", - "0", "0.02", - "0", "0.05", - "0", "0.07", + "0.0", "0.0", + "0.0", "0.02", + "0.0", "0.05", + "0.0", "0.07", "0.1", "0.10", - "1000", "1000.09", - "-1000", "-999.95" + "1000.0", "1000.09", + "-1000.0", "-999.95" ); } @@ -523,15 +523,15 @@ public void accept(String s1, String s2) { checkRoundTowardsZero(s1, s2); } }, - "-1", "-1.0", - "0", "-0.7", - "0", "-0.5", - "0", "-0.2", - "0", "0.0", - "0", "0.2", - "0", "0.5", - "0", "0.7", - "1", "1.0" + "-1.0", "-1.0", + "0.0", "-0.7", + "0.0", "-0.5", + "0.0", "-0.2", + "0.0", "0.0", + "0.0", "0.2", + "0.0", "0.5", + "0.0", "0.7", + "1.0", "1.0" ); } @@ -557,17 +557,17 @@ public Long apply(Long aLong) { }, a, b, message); } }, - "-1", "-1.0", - "-1", "-0.7", - "-1", "-0.5", - "0", "-0.2", - "0", "0.0", - "0", "0.2", - "1", "0.5", - "1", "0.7", - "1", "1.0", - "10000", "9999.5", - "-10000", "-9999.5" + "-1.0", "-1.0", + "-1.0", "-0.7", + "-1.0", "-0.5", + "0.0", "-0.2", + "0.0", "0.0", + "0.0", "0.2", + "1.0", "0.5", + "1.0", "0.7", + "1.0", "1.0", + "10000.0", "9999.5", + "-10000.0", "-9999.5" ); @Decimal final long multiple = Decimal64Utils.parse("0.1"); @@ -588,14 +588,14 @@ public Long apply(Long x) { "-0.1", "-0.10", "-0.1", "-0.07", "-0.1", "-0.05", - "0", "-0.02", - "0", "0.0", - "0", "0.02", + "0.0", "-0.02", + "0.0", "0.0", + "0.0", "0.02", "0.1", "0.05", "0.1", "0.07", "0.1", "0.10", - "1000", "999.95", - "-1000", "-999.95" + "1000.0", "999.95", + "-1000.0", "-999.95" ); } @@ -616,17 +616,17 @@ public Long apply(Long aLong) { }, a, b, message); } }, - "-1", "-1.0", - "-1", "-0.7", - "0", "-0.5", - "0", "-0.2", - "0", "0.0", - "0", "0.2", - "0", "0.5", - "1", "0.7", - "1", "1.0", - "10000", "9999.5", - "-10000", "-9999.5" + "-1.0", "-1.0", + "-1.0", "-0.7", + "0.0", "-0.5", + "0.0", "-0.2", + "0.0", "0.0", + "0.0", "0.2", + "0.0", "0.5", + "1.0", "0.7", + "1.0", "1.0", + "10000.0", "9999.5", + "-10000.0", "-9999.5" ); @Decimal final long multiple = Decimal64Utils.parse("0.1"); @@ -646,15 +646,15 @@ public Long apply(Long x) { }, "-0.1", "-0.10", "-0.1", "-0.07", - "0", "-0.05", - "0", "-0.02", - "0", "0.0", - "0", "0.02", - "0", "0.05", + "0.0", "-0.05", + "0.0", "-0.02", + "0.0", "0.0", + "0.0", "0.02", + "0.0", "0.05", "0.1", "0.07", "0.1", "0.10", - "1000", "999.95", - "-1000", "-999.95" + "1000.0", "999.95", + "-1000.0", "-999.95" ); } diff --git a/java/dfp/src/test/java/com/epam/deltix/dfp/FromDoubleTest.java b/java/dfp/src/test/java/com/epam/deltix/dfp/FromDoubleTest.java index d3465914..74f852e2 100644 --- a/java/dfp/src/test/java/com/epam/deltix/dfp/FromDoubleTest.java +++ b/java/dfp/src/test/java/com/epam/deltix/dfp/FromDoubleTest.java @@ -99,10 +99,10 @@ public void accept(String s) { FromDoubleTest.checkDecimalDoubleConversion(s); } }, - "0", "NaN", "Infinity", "-Infinity", + "0.0", "NaN", "Infinity", "-Infinity", "1E0", "1000000000000000E0", "0.000000009412631", "0.95285752", - "9.2", "25107188000000000000000000000000000000000000000000000000", + "9.2", "25107188000000000000000000000000000000000000000000000000.0", "9.888888888888888", // Exponent limits "-1E-308", "-1000000000000000E-322" diff --git a/java/dfp/src/test/java/com/epam/deltix/dfp/JavaImplTest.java b/java/dfp/src/test/java/com/epam/deltix/dfp/JavaImplTest.java index 36767372..17202dc7 100644 --- a/java/dfp/src/test/java/com/epam/deltix/dfp/JavaImplTest.java +++ b/java/dfp/src/test/java/com/epam/deltix/dfp/JavaImplTest.java @@ -4,6 +4,7 @@ import org.junit.Assert; import org.junit.Test; +import java.io.CharArrayWriter; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -121,25 +122,25 @@ public void appendTo() throws IOException { final StringBuilder string = new StringBuilder(); string.setLength(0); - assertTrue("NaN".equals(JavaImpl.appendToRefImpl(Decimal64Utils.NaN, string).toString())); + assertEquals("NaN", appendToRefImpl(Decimal64Utils.NaN, string).toString()); string.setLength(0); - assertTrue("Infinity".equals(JavaImpl.appendToRefImpl(Decimal64Utils.POSITIVE_INFINITY, string).toString())); + assertEquals("Infinity", appendToRefImpl(Decimal64Utils.POSITIVE_INFINITY, string).toString()); string.setLength(0); - assertTrue("-Infinity".equals(JavaImpl.appendToRefImpl(Decimal64Utils.NEGATIVE_INFINITY, string).toString())); + assertEquals("-Infinity", appendToRefImpl(Decimal64Utils.NEGATIVE_INFINITY, string).toString()); string.setLength(0); - assertTrue("100000010000".equals(JavaImpl.appendToRefImpl(Decimal64Utils.fromDouble(10000001E+04), string).toString())); + assertEquals("100000010000.0", JavaImpl.appendToRefImpl(Decimal64Utils.fromDouble(10000001E+04), string).toString()); string.setLength(0); - assertTrue("10000001".equals(JavaImpl.appendToRefImpl(Decimal64Utils.fromDouble(10000001), string).toString())); + assertEquals("10000001.0", JavaImpl.appendToRefImpl(Decimal64Utils.fromDouble(10000001), string).toString()); string.setLength(0); - assertTrue("1000.0001".equals(JavaImpl.appendToRefImpl(Decimal64Utils.fromDouble(10000001E-04), string).toString())); + assertEquals("1000.0001", JavaImpl.appendToRefImpl(Decimal64Utils.fromDouble(10000001E-04), string).toString()); string.setLength(0); - assertTrue("9.2".equals(JavaImpl.appendToRefImpl(Decimal64Utils.fromDecimalDouble(92E-01), string).toString())); + assertEquals("9.2", appendToRefImpl(Decimal64Utils.fromDecimalDouble(92E-01), string).toString()); } @Test @@ -215,12 +216,12 @@ public void toStringSpecialCase1() { @Test public void toStringSpecialCase2() { - checkToString(null, "31800000013474D8", "202150", "+20215000E-2"); + checkToString(null, "31800000013474D8", "202150.0", "+20215000E-2"); } @Test public void toStringSpecialCase3() { - checkToString(null, "8020000000000000", "0", "-0E-397"); + checkToString(null, "8020000000000000", "0.0", "-0E-397"); } @Test @@ -994,4 +995,59 @@ private static BigInteger unsignedLongToBigInteger(long x) { return new BigInteger(p); } + + @Test + public void issue84ToStringAsDouble() throws IOException { + for (final double dbl : new double[]{1234, 12340, 0.01234, 1234.567, 0.0, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NaN}) { + final String str = Double.toString(dbl); + + final Decimal64 d64 = Decimal64.parse(str); + final long d64u = Decimal64.toUnderlying(d64); + final Decimal64Parts dParts = toParts(d64u); + + for (int shift = 0, f = 1; shift < 6; ++shift, f *= 10) { + final long d64up = shift == 0 ? d64u : JavaImplMul.get_BID64(dParts.signMask, dParts.exponent - shift, dParts.coefficient * f); + + assertEquals(str, Decimal64Utils.toString(d64up)); + + { + final CharArrayWriter writer = new CharArrayWriter(1024); + Decimal64Utils.appendTo(d64up, writer); + assertEquals(str, writer.toString()); + } + + { + final StringBuilder sb = new StringBuilder(); + Decimal64Utils.appendTo(d64up, sb); + assertEquals(str, sb.toString()); + } + + if (!d64.isFinite()) + break; + } + } + } + + @Test + public void issue84ToStringAsDoublePart2() throws IOException { + final long d64u = Decimal64Utils.parse("1234.0"); + final Decimal64Parts dParts = toParts(d64u); + + final long d64Up = JavaImplMul.get_BID64(dParts.signMask, dParts.exponent - 6 - 6, dParts.coefficient * 1000_000); + final String strUp = "0.001234"; + + assertEquals(strUp, Decimal64Utils.toString(d64Up)); + + { + final CharArrayWriter writer = new CharArrayWriter(1024); + Decimal64Utils.appendTo(d64Up, writer); + assertEquals(strUp, writer.toString()); + } + + { + final StringBuilder sb = new StringBuilder(); + Decimal64Utils.appendTo(d64Up, sb); + assertEquals(strUp, sb.toString()); + } + } } From 9e3bb9fd6085be33bd80f1658cab0400cffa2417 Mon Sep 17 00:00:00 2001 From: Andrei Davydov Date: Tue, 19 Sep 2023 19:47:18 +0300 Subject: [PATCH 2/5] Java: Support custom decimal separators for toString() and similar methods. --- .../java/com/epam/deltix/dfp/Decimal64.java | 22 +- .../com/epam/deltix/dfp/Decimal64Utils.java | 258 +++++++++++++++++- .../java/com/epam/deltix/dfp/JavaImpl.java | 74 ++--- .../com/epam/deltix/dfp/JavaImplParse.java | 137 +++++----- .../com/epam/deltix/dfp/JavaImplTest.java | 39 +-- .../epam/deltix/dfp/FormattingBenchmark.java | 15 +- 6 files changed, 405 insertions(+), 140 deletions(-) diff --git a/java/dfp/src/main/java/com/epam/deltix/dfp/Decimal64.java b/java/dfp/src/main/java/com/epam/deltix/dfp/Decimal64.java index 053a0dad..711d1502 100644 --- a/java/dfp/src/main/java/com/epam/deltix/dfp/Decimal64.java +++ b/java/dfp/src/main/java/com/epam/deltix/dfp/Decimal64.java @@ -1011,7 +1011,27 @@ public static Decimal64 parse(final CharSequence text) { public static Decimal64 tryParse(final CharSequence text, final int startIndex, final int endIndex, final Decimal64 defaultValue) { JavaImplParse.FloatingPointStatusFlag fpsf = Decimal64Utils.tlsFpst.get(); - final long ret = JavaImplParse.bid64_from_string(text, startIndex, endIndex, fpsf, JavaImpl.BID_ROUNDING_TO_NEAREST); + final long ret = JavaImplParse.bid64_from_string(text, startIndex, endIndex, fpsf, JavaImpl.BID_ROUNDING_TO_NEAREST, Decimal64Utils.DECIMAL_MARK_ANY); + if ((fpsf.status & JavaImplParse.BID_INVALID_FORMAT) != 0) + return defaultValue; + return Decimal64.fromUnderlying(ret); + } + + /** + * Tries to parse a dfp floating-point value from the given textual representation. + * Returns the default value in case of fail. + * + * @param text Textual representation of dfp floating-point value. + * @param startIndex Index of character to start parsing at. + * @param endIndex Index of character to stop parsing at, non-inclusive. + * @param decimalMarks A decimal separators used to separate the integer part from the fractional part. + * @param defaultValue Default value in case of fail. + * @return 64-bit dfp floating-point. + */ + public static Decimal64 tryParse(final CharSequence text, final int startIndex, final int endIndex, final String decimalMarks, + final Decimal64 defaultValue) { + JavaImplParse.FloatingPointStatusFlag fpsf = Decimal64Utils.tlsFpst.get(); + final long ret = JavaImplParse.bid64_from_string(text, startIndex, endIndex, fpsf, JavaImpl.BID_ROUNDING_TO_NEAREST, decimalMarks); if ((fpsf.status & JavaImplParse.BID_INVALID_FORMAT) != 0) return defaultValue; return Decimal64.fromUnderlying(ret); diff --git a/java/dfp/src/main/java/com/epam/deltix/dfp/Decimal64Utils.java b/java/dfp/src/main/java/com/epam/deltix/dfp/Decimal64Utils.java index 3c12ea94..cff76d61 100644 --- a/java/dfp/src/main/java/com/epam/deltix/dfp/Decimal64Utils.java +++ b/java/dfp/src/main/java/com/epam/deltix/dfp/Decimal64Utils.java @@ -153,6 +153,15 @@ public class Decimal64Utils { /// endregion + /// region String formatting + + public static final char DECIMAL_MARK_DOT = '.'; + public static final char DECIMAL_MARK_COMMA = ','; + public static final char DECIMAL_MARK_DEFAULT = DECIMAL_MARK_DOT; + public static final String DECIMAL_MARK_ANY = "" + DECIMAL_MARK_DOT + DECIMAL_MARK_COMMA; + + /// endregion + /// region Object Implementation /** @@ -187,11 +196,19 @@ public static int hashCode(@Decimal final long value) { public static String toString(@Decimal final long value) { - return JavaImpl.fastToString(value); + return JavaImpl.fastToString(value, DECIMAL_MARK_DEFAULT); + } + + public static String toString(@Decimal final long value, final char decimalMark) { + return JavaImpl.fastToString(value, decimalMark); } public static String toScientificString(@Decimal final long value) { - return JavaImpl.fastToScientificString(value); + return JavaImpl.fastToScientificString(value, DECIMAL_MARK_DEFAULT); + } + + public static String toScientificString(@Decimal final long value, final char decimalMark) { + return JavaImpl.fastToScientificString(value, decimalMark); } static String toDebugString(@Decimal final long value) { @@ -1232,7 +1249,22 @@ public static long canonize(@Decimal final long value) { * @throws IOException from {@link Appendable#append(char)} */ public static Appendable appendTo(@Decimal final long value, final Appendable appendable) throws IOException { - return JavaImpl.fastAppendToAppendable(value, appendable); + return JavaImpl.fastAppendToAppendable(value, DECIMAL_MARK_DEFAULT, appendable); + } + + /** + * Append string representation of {@code DFP} {@code value} to {@link Appendable} {@code appendable} + *

+ * Same as {@code appendable.append(value.toString())}, but more efficient. + * + * @param value {@code DFP64} argument + * @param decimalMark A decimal separator used to separate the integer part from the fractional part. + * @param appendable {@link Appendable} instance to which the string representation of the {@code value} will be appended + * @return the 2nd argument ({@link Appendable} {@code appendable}) + * @throws IOException from {@link Appendable#append(char)} + */ + public static Appendable appendTo(@Decimal final long value, final char decimalMark, final Appendable appendable) throws IOException { + return JavaImpl.fastAppendToAppendable(value, decimalMark, appendable); } /** @@ -1246,7 +1278,22 @@ public static Appendable appendTo(@Decimal final long value, final Appendable ap * @throws IOException from {@link Appendable#append(char)} */ public static Appendable scientificAppendTo(@Decimal final long value, final Appendable appendable) throws IOException { - return JavaImpl.fastScientificAppendToAppendable(value, appendable); + return JavaImpl.fastScientificAppendToAppendable(value, DECIMAL_MARK_DEFAULT, appendable); + } + + /** + * Append string representation of {@code DFP} {@code value} to {@link Appendable} {@code appendable} + *

+ * Same as {@code appendable.append(value.toString())}, but more efficient. + * + * @param value {@code DFP64} argument + * @param decimalMark A decimal separator used to separate the integer part from the fractional part. + * @param appendable {@link Appendable} instance to which the string representation of the {@code value} will be appended + * @return the 2nd argument ({@link Appendable} {@code appendable}) + * @throws IOException from {@link Appendable#append(char)} + */ + public static Appendable scientificAppendTo(@Decimal final long value, final char decimalMark, final Appendable appendable) throws IOException { + return JavaImpl.fastScientificAppendToAppendable(value, decimalMark, appendable); } /** @@ -1263,6 +1310,22 @@ public static Appendable scientificAppendToChecked(@Decimal final long value, fi return scientificAppendTo(value, appendable); } + /** + * Implements {@link Decimal64#scientificAppendTo(Appendable)}, adds null check; do not use directly. + * + * @param value DFP argument + * @param decimalMark A decimal separator used to separate the integer part from the fractional part. + * @param appendable an object, implementing Appendable interface + * @return .. + * @throws IOException from {@link Appendable#append(char)} + */ + @Deprecated + public static Appendable scientificAppendToChecked(@Decimal final long value, final char decimalMark, final Appendable appendable) throws IOException { + checkNull(value); + return scientificAppendTo(value, decimalMark, appendable); + } + + /** * Append string representation of {@code DFP} value to {@link StringBuilder} {@code sb} *

@@ -1273,7 +1336,21 @@ public static Appendable scientificAppendToChecked(@Decimal final long value, fi * @return the value of 2nd argument ({@link StringBuilder} {@code sb}) */ public static StringBuilder appendTo(@Decimal final long value, final StringBuilder sb) { - return JavaImpl.fastAppendToStringBuilder(value, sb); + return JavaImpl.fastAppendToStringBuilder(value, DECIMAL_MARK_DEFAULT, sb); + } + + /** + * Append string representation of {@code DFP} value to {@link StringBuilder} {@code sb} + *

+ * Same as {@code sb.append(value.toString());}, but more efficient. + * + * @param value {@code DFP64} argument + * @param decimalMark A decimal separator used to separate the integer part from the fractional part. + * @param sb {@link StringBuilder} instance to which the string representation of the {@code value} will be appended + * @return the value of 2nd argument ({@link StringBuilder} {@code sb}) + */ + public static StringBuilder appendTo(@Decimal final long value, final char decimalMark, final StringBuilder sb) { + return JavaImpl.fastAppendToStringBuilder(value, decimalMark, sb); } /** @@ -1286,7 +1363,21 @@ public static StringBuilder appendTo(@Decimal final long value, final StringBuil * @return the value of 2nd argument ({@link StringBuilder} {@code sb}) */ public static StringBuilder scientificAppendTo(@Decimal final long value, final StringBuilder sb) { - return JavaImpl.fastScientificAppendToStringBuilder(value, sb); + return JavaImpl.fastScientificAppendToStringBuilder(value, DECIMAL_MARK_DEFAULT, sb); + } + + /** + * Append string representation of {@code DFP} value to {@link StringBuilder} {@code sb} + *

+ * Same as {@code sb.append(value.toString());}, but more efficient. + * + * @param value {@code DFP64} argument + * @param decimalMark A decimal separator used to separate the integer part from the fractional part. + * @param sb {@link StringBuilder} instance to which the string representation of the {@code value} will be appended + * @return the value of 2nd argument ({@link StringBuilder} {@code sb}) + */ + public static StringBuilder scientificAppendTo(@Decimal final long value, final char decimalMark, final StringBuilder sb) { + return JavaImpl.fastScientificAppendToStringBuilder(value, decimalMark, sb); } /** @@ -1302,6 +1393,20 @@ public static StringBuilder scientificAppendToChecked(@Decimal final long value, return scientificAppendTo(value, sb); } + /** + * Implements {@link Decimal64#scientificAppendTo(StringBuilder)}, adds null check; do not use directly. + * + * @param value DFP argument + * @param decimalMark A decimal separator used to separate the integer part from the fractional part. + * @param sb {@link StringBuilder} instance to which the string representation of the {@code value} will be appended + * @return .. + */ + @Deprecated + public static StringBuilder scientificAppendToChecked(@Decimal final long value, final char decimalMark, final StringBuilder sb) { + checkNull(value); + return scientificAppendTo(value, decimalMark, sb); + } + /** * Try parse a dfp floating-point value from the given textual representation. *

@@ -1324,7 +1429,34 @@ public static StringBuilder scientificAppendToChecked(@Decimal final long value, @Decimal public static boolean tryParse(final CharSequence text, final int startIndex, final int endIndex, final Decimal64Status outStatus) { - outStatus.underlying = JavaImplParse.bid64_from_string(text, startIndex, endIndex, outStatus, JavaImpl.BID_ROUNDING_TO_NEAREST); + outStatus.underlying = JavaImplParse.bid64_from_string(text, startIndex, endIndex, outStatus, JavaImpl.BID_ROUNDING_TO_NEAREST, DECIMAL_MARK_ANY); + return outStatus.isExact(); + } + + /** + * Try parse a dfp floating-point value from the given textual representation. + *

+ * Besides regular floating-point values (possibly in scientific notation) the following special cases are accepted: + *

+ * + * @param text Textual representation of dfp floating-point value. + * @param startIndex Index of character to start parsing at. + * @param endIndex Index of character to stop parsing at, non-inclusive. + * @param decimalMarks A decimal separators used to separate the integer part from the fractional part. + * @param outStatus The parsing output status and value. + * @return Return {@code true} if value was parsed exactly without rounding. + */ + @Decimal + public static boolean tryParse(final CharSequence text, final int startIndex, final int endIndex, final String decimalMarks, + final Decimal64Status outStatus) { + outStatus.underlying = JavaImplParse.bid64_from_string(text, startIndex, endIndex, outStatus, JavaImpl.BID_ROUNDING_TO_NEAREST, decimalMarks); return outStatus.isExact(); } @@ -1350,7 +1482,38 @@ public static boolean tryParse(final CharSequence text, final int startIndex, fi @Decimal public static long parse(final CharSequence text, final int startIndex, final int endIndex) { JavaImplParse.FloatingPointStatusFlag fpsf = tlsFpst.get(); - final long ret = JavaImplParse.bid64_from_string(text, startIndex, endIndex, fpsf, JavaImpl.BID_ROUNDING_TO_NEAREST); + final long ret = JavaImplParse.bid64_from_string(text, startIndex, endIndex, fpsf, JavaImpl.BID_ROUNDING_TO_NEAREST, DECIMAL_MARK_ANY); + if ((fpsf.status & JavaImplParse.BID_INVALID_FORMAT) != 0) + throw new NumberFormatException("Input string is not in a correct format."); +// else if ((fpsf.value & JavaImplParse.BID_INEXACT_EXCEPTION) != 0) +// throw new NumberFormatException("Can't convert input string to value without precision loss."); + return ret; + } + + /** + * Parses a dfp floating-point value from the given textual representation. + *

+ * Besides regular floating-point values (possibly in scientific notation) the following special cases are accepted: + *

+ * + * @param text Textual representation of dfp floating-point value. + * @param startIndex Index of character to start parsing at. + * @param endIndex Index of character to stop parsing at, non-inclusive. + * @param decimalMarks A decimal separators used to separate the integer part from the fractional part. + * @return parsed 64-bit decimal floating point value. + * @throws NumberFormatException if {@code text} does not contain valid dfp value. + */ + @Decimal + public static long parse(final CharSequence text, final int startIndex, final int endIndex, final String decimalMarks) { + JavaImplParse.FloatingPointStatusFlag fpsf = tlsFpst.get(); + final long ret = JavaImplParse.bid64_from_string(text, startIndex, endIndex, fpsf, JavaImpl.BID_ROUNDING_TO_NEAREST, decimalMarks); if ((fpsf.status & JavaImplParse.BID_INVALID_FORMAT) != 0) throw new NumberFormatException("Input string is not in a correct format."); // else if ((fpsf.value & JavaImplParse.BID_INEXACT_EXCEPTION) != 0) @@ -1424,7 +1587,29 @@ public static long parse(final CharSequence text) { @Decimal public static long tryParse(final CharSequence text, final int startIndex, final int endIndex, @Decimal final long defaultValue) { JavaImplParse.FloatingPointStatusFlag fpsf = tlsFpst.get(); - final long ret = JavaImplParse.bid64_from_string(text, startIndex, endIndex, fpsf, JavaImpl.BID_ROUNDING_TO_NEAREST); + final long ret = JavaImplParse.bid64_from_string(text, startIndex, endIndex, fpsf, JavaImpl.BID_ROUNDING_TO_NEAREST, DECIMAL_MARK_ANY); + if ((fpsf.status & JavaImplParse.BID_INVALID_FORMAT) != 0) + return defaultValue; +// else if ((fpsf.value & JavaImplParse.BID_INEXACT_EXCEPTION) != 0) +// throw new NumberFormatException("Can't convert input string to value without precision loss."); + return ret; + } + + /** + * Tries to parse a dfp floating-point value from the given textual representation. + * Returns the default value in case of fail. + * + * @param text Textual representation of dfp floating-point value. + * @param startIndex Index of character to start parsing at. + * @param endIndex Index of character to stop parsing at, non-inclusive. + * @param decimalMarks A decimal separators used to separate the integer part from the fractional part. + * @param defaultValue Default value in case of fail. + * @return parsed 64-bit decimal floating point value. + */ + @Decimal + public static long tryParse(final CharSequence text, final int startIndex, final int endIndex, final String decimalMarks, @Decimal final long defaultValue) { + JavaImplParse.FloatingPointStatusFlag fpsf = tlsFpst.get(); + final long ret = JavaImplParse.bid64_from_string(text, startIndex, endIndex, fpsf, JavaImpl.BID_ROUNDING_TO_NEAREST, decimalMarks); if ((fpsf.status & JavaImplParse.BID_INVALID_FORMAT) != 0) return defaultValue; // else if ((fpsf.value & JavaImplParse.BID_INEXACT_EXCEPTION) != 0) @@ -2370,6 +2555,21 @@ public static Appendable appendToChecked(@Decimal final long value, final Append return appendTo(value, appendable); } + /** + * Implements {@link Decimal64#appendTo(Appendable)}, adds null check; do not use directly. + * + * @param value DFP argument + * @param decimalMark A decimal separator used to separate the integer part from the fractional part. + * @param appendable an object, implementing Appendable interface + * @return .. + * @throws IOException from {@link Appendable#append(char)} + */ + @Deprecated + public static Appendable appendToChecked(@Decimal final long value, final char decimalMark, final Appendable appendable) throws IOException { + checkNull(value); + return appendTo(value, decimalMark, appendable); + } + /** * Implements {@link Decimal64#appendTo(StringBuilder)}, adds null check; do not use directly. * @@ -2383,6 +2583,20 @@ public static StringBuilder appendToChecked(@Decimal final long value, final Str return appendTo(value, stringBuilder); } + /** + * Implements {@link Decimal64#appendTo(StringBuilder)}, adds null check; do not use directly. + * + * @param value DFP argument + * @param decimalMark A decimal separator used to separate the integer part from the fractional part. + * @param stringBuilder StringBuilder argument + * @return .. + */ + @Deprecated + public static StringBuilder appendToChecked(@Decimal final long value, final char decimalMark, final StringBuilder stringBuilder) { + checkNull(value); + return appendTo(value, decimalMark, stringBuilder); + } + /** * Implements {@link Decimal64#toString()}, adds null checks; do not use directly. * @@ -2395,6 +2609,19 @@ public static String toStringChecked(@Decimal final long value) { return toString(value); } + /** + * Implements {@link Decimal64#toString()}, adds null checks; do not use directly. + * + * @param value DFP argument + * @param decimalMark A decimal separator used to separate the integer part from the fractional part. + * @return .. + */ + @Deprecated + public static String toStringChecked(@Decimal final long value, final char decimalMark) { + checkNull(value); + return toString(value, decimalMark); + } + /** * Implements {@link Decimal64#toScientificString()}, adds null checks; do not use directly. * @@ -2407,6 +2634,19 @@ public static String toScientificStringChecked(@Decimal final long value) { return toScientificString(value); } + /** + * Implements {@link Decimal64#toScientificString()}, adds null checks; do not use directly. + * + * @param value DFP argument + * @param decimalMark A decimal separator used to separate the integer part from the fractional part. + * @return .. + */ + @Deprecated + public static String toScientificStringChecked(@Decimal final long value, final char decimalMark) { + checkNull(value); + return toScientificString(value, decimalMark); + } + /** * Implements {@link Decimal64#equals(Decimal64)}, adds null checks; do not use directly. * diff --git a/java/dfp/src/main/java/com/epam/deltix/dfp/JavaImpl.java b/java/dfp/src/main/java/com/epam/deltix/dfp/JavaImpl.java index 1960a625..635dbcde 100644 --- a/java/dfp/src/main/java/com/epam/deltix/dfp/JavaImpl.java +++ b/java/dfp/src/main/java/com/epam/deltix/dfp/JavaImpl.java @@ -367,7 +367,7 @@ public static long fromDecimalDouble(final double x) { // } // }; - public static Appendable appendToRefImpl(final long value, final Appendable appendable) throws IOException { + public static Appendable appendToRefImpl(final long value, final char decimalMark, final Appendable appendable) throws IOException { if (isNull(value)) { return appendable.append("null"); } @@ -420,7 +420,7 @@ public static Appendable appendToRefImpl(final long value, final Appendable appe } if (0 == partsCoefficient) - return appendable.append("0.0"); + return appendable.append("0" + decimalMark + "0"); if (value < 0) appendable.append('-'); @@ -432,21 +432,21 @@ public static Appendable appendToRefImpl(final long value, final Appendable appe appendLongTo(partsCoefficient, appendable, digits); for (int i = 0; i < exponent; i += 1) appendable.append('0'); - appendable.append(".0"); + appendable.append(decimalMark).append('0'); } else if (digits + exponent > 0) { final long integralPart = partsCoefficient / POWERS_OF_TEN[-exponent]; final long fractionalPart = partsCoefficient % POWERS_OF_TEN[-exponent]; appendLongTo(integralPart, appendable); if (fractionalPart != 0L) { - appendable.append('.'); + appendable.append(decimalMark); for (int i = numberOfDigits(fractionalPart); i < -exponent; i += 1) appendable.append('0'); appendLongTo(dropTrailingZeros(fractionalPart), appendable); } else { - appendable.append(".0"); + appendable.append(decimalMark).append('0'); } } else { - appendable.append("0."); + appendable.append('0').append(decimalMark); for (int i = digits + exponent; i < 0; i += 1) appendable.append('0'); appendLongTo(dropTrailingZeros(partsCoefficient), appendable); @@ -496,7 +496,7 @@ private static char[] makeBcdTable(final int tenPowerMaxIndex) { return table; } - public static String fastToString(final long value) { + public static String fastToString(final long value, final char decimalMark) { if (isNull(value)) return "null"; @@ -549,7 +549,7 @@ public static String fastToString(final long value) { } if (partsCoefficient == 0) - return "0.0"; + return "0" + decimalMark + "0"; int exponent = partsExponent - EXPONENT_BIAS; @@ -558,7 +558,7 @@ public static String fastToString(final long value) { if (exponent >= 0) { int bi = buffer.length; buffer[--bi] = '0'; - buffer[--bi] = '.'; + buffer[--bi] = decimalMark; for (int i = 0; i < exponent; ++i) buffer[--bi] = '0'; @@ -596,7 +596,7 @@ public static String fastToString(final long value) { bi = buffer.length + exponent; /* buffer.length - (-exponent) */ - buffer[--bi] = '.'; + buffer[--bi] = decimalMark; while (integralPart > 0) { bi = formatUIntFromBcdTable((int) (integralPart % BCD_DIVIDER), buffer, bi); @@ -613,7 +613,7 @@ public static String fastToString(final long value) { while (buffer[be - 1] == '0') --be; - if (buffer[be - 1] == '.' && be < buffer.length) + if (buffer[be - 1] == decimalMark && be < buffer.length) buffer[be++] = '0'; return new String(buffer, bi, be - bi); @@ -631,7 +631,7 @@ public static String fastToString(final long value) { bi = buffer.length + exponent; /* buffer.length - (-exponent) */ - buffer[--bi] = '.'; + buffer[--bi] = decimalMark; buffer[--bi] = '0'; if (value < 0) @@ -646,7 +646,7 @@ public static String fastToString(final long value) { } } - public static String fastToScientificString(final long value) { + public static String fastToScientificString(final long value, final char decimalMark) { if (isNull(value)) return "null"; @@ -721,7 +721,7 @@ public static String fastToScientificString(final long value) { bi--; buffer[bi] = buffer[bi + 1]; - buffer[bi + 1] = '.'; + buffer[bi + 1] = decimalMark; if (value < 0) buffer[--bi] = '-'; @@ -738,7 +738,7 @@ public static String fastToScientificString(final long value) { } // Copy-paste of the fastToString - public static StringBuilder fastAppendToStringBuilder(final long value, final StringBuilder stringBuilder) { + public static StringBuilder fastAppendToStringBuilder(final long value, final char decimalMark, final StringBuilder stringBuilder) { if (isNull(value)) return stringBuilder.append("null"); @@ -791,7 +791,7 @@ public static StringBuilder fastAppendToStringBuilder(final long value, final St } if (partsCoefficient == 0) - return stringBuilder.append("0.0"); + return stringBuilder.append('0').append(decimalMark).append('0'); int exponent = partsExponent - EXPONENT_BIAS; @@ -800,7 +800,7 @@ public static StringBuilder fastAppendToStringBuilder(final long value, final St if (exponent >= 0) { int bi = buffer.length; buffer[--bi] = '0'; - buffer[--bi] = '.'; + buffer[--bi] = decimalMark; for (int i = 0; i < exponent; ++i) buffer[--bi] = '0'; @@ -838,7 +838,7 @@ public static StringBuilder fastAppendToStringBuilder(final long value, final St bi = buffer.length + exponent; /* buffer.length - (-exponent) */ - buffer[--bi] = '.'; + buffer[--bi] = decimalMark; while (integralPart > 0) { bi = formatUIntFromBcdTable((int) (integralPart % BCD_DIVIDER), buffer, bi); @@ -855,7 +855,7 @@ public static StringBuilder fastAppendToStringBuilder(final long value, final St while (buffer[be - 1] == '0') --be; - if (buffer[be - 1] == '.' && be < buffer.length) + if (buffer[be - 1] == decimalMark && be < buffer.length) buffer[be++] = '0'; return stringBuilder.append(buffer, bi, be - bi); @@ -873,7 +873,7 @@ public static StringBuilder fastAppendToStringBuilder(final long value, final St bi = buffer.length + exponent; /* buffer.length - (-exponent) */ - buffer[--bi] = '.'; + buffer[--bi] = decimalMark; buffer[--bi] = '0'; if (value < 0) @@ -889,7 +889,7 @@ public static StringBuilder fastAppendToStringBuilder(final long value, final St } // Copy-paste of the fastToScientificString - public static StringBuilder fastScientificAppendToStringBuilder(final long value, final StringBuilder stringBuilder) { + public static StringBuilder fastScientificAppendToStringBuilder(final long value, final char decimalMark, final StringBuilder stringBuilder) { if (isNull(value)) return stringBuilder.append("null"); @@ -964,7 +964,7 @@ public static StringBuilder fastScientificAppendToStringBuilder(final long value bi--; buffer[bi] = buffer[bi + 1]; - buffer[bi + 1] = '.'; + buffer[bi + 1] = decimalMark; if (value < 0) buffer[--bi] = '-'; @@ -1037,7 +1037,7 @@ protected MutableCharBuffer initialValue() { }; // Copy-paste of the fastToString - public static Appendable fastAppendToAppendable(final long value, final Appendable appendable) throws IOException { + public static Appendable fastAppendToAppendable(final long value, final char decimalMark, final Appendable appendable) throws IOException { if (isNull(value)) return appendable.append("null"); @@ -1090,7 +1090,7 @@ public static Appendable fastAppendToAppendable(final long value, final Appendab } if (partsCoefficient == 0) - return appendable.append("0.0"); + return appendable.append('0').append(decimalMark).append('0'); int exponent = partsExponent - EXPONENT_BIAS; @@ -1100,7 +1100,7 @@ public static Appendable fastAppendToAppendable(final long value, final Appendab if (exponent >= 0) { int bi = buffer.length; buffer[--bi] = '0'; - buffer[--bi] = '.'; + buffer[--bi] = decimalMark; for (int i = 0; i < exponent; ++i) buffer[--bi] = '0'; @@ -1138,7 +1138,7 @@ public static Appendable fastAppendToAppendable(final long value, final Appendab bi = buffer.length + exponent; /* buffer.length - (-exponent) */ - buffer[--bi] = '.'; + buffer[--bi] = decimalMark; while (integralPart > 0) { bi = formatUIntFromBcdTable((int) (integralPart % BCD_DIVIDER), buffer, bi); @@ -1155,7 +1155,7 @@ public static Appendable fastAppendToAppendable(final long value, final Appendab while (buffer[be - 1] == '0') --be; - if (buffer[be - 1] == '.' && be < buffer.length) + if (buffer[be - 1] == decimalMark && be < buffer.length) buffer[be++] = '0'; return appendable.append(charBuffer.setRange(bi, be - bi)); @@ -1173,7 +1173,7 @@ public static Appendable fastAppendToAppendable(final long value, final Appendab bi = buffer.length + exponent; /* buffer.length - (-exponent) */ - buffer[--bi] = '.'; + buffer[--bi] = decimalMark; buffer[--bi] = '0'; if (value < 0) @@ -1189,7 +1189,7 @@ public static Appendable fastAppendToAppendable(final long value, final Appendab } // Copy-paste of the fastToScientificString - public static Appendable fastScientificAppendToAppendable(final long value, final Appendable appendable) throws IOException { + public static Appendable fastScientificAppendToAppendable(final long value, final char decimalMark, final Appendable appendable) throws IOException { if (isNull(value)) return appendable.append("null"); @@ -1265,7 +1265,7 @@ public static Appendable fastScientificAppendToAppendable(final long value, fina bi--; buffer[bi] = buffer[bi + 1]; - buffer[bi + 1] = '.'; + buffer[bi + 1] = decimalMark; if (value < 0) buffer[--bi] = '-'; @@ -1429,7 +1429,7 @@ private static NumberFormatException invalidFormat(final CharSequence s, final i return new NumberFormatException(s.subSequence(si, ei).toString()); } - public static long parse(final CharSequence s, final int si, final int ei, final int roundingMode) { + static long parse(final CharSequence s, final int si, final int ei, final int roundingMode, final String decimalMarks) { char c; int p = si; boolean sign = false; @@ -1446,7 +1446,7 @@ public static long parse(final CharSequence s, final int si, final int ei, final c = p < ei ? s.charAt(p) : 0; } - if (c != '.' && (c < '0' || c > '9')) { + if (decimalMarks.indexOf(c) < 0 && (c < '0' || c > '9')) { if (TextUtils.equalsIgnoringCase(s, p, ei, "Infinity") || TextUtils.equalsIgnoringCase(s, p, ei, "Inf")) return sign ? JavaImpl.NEGATIVE_INFINITY : JavaImpl.POSITIVE_INFINITY; @@ -1459,8 +1459,8 @@ public static long parse(final CharSequence s, final int si, final int ei, final boolean seenRadixPoint = false; int leadingZerosAfterPoint = 0; - if (c == '0' || c == '.') { - if (c == '.') { + if (c == '0' || decimalMarks.indexOf(c) >= 0) { + if (decimalMarks.indexOf(c) >= 0) { seenRadixPoint = true; p += 1; @@ -1474,7 +1474,7 @@ public static long parse(final CharSequence s, final int si, final int ei, final if (seenRadixPoint) leadingZerosAfterPoint += 1; - if (c == '.') { + if (decimalMarks.indexOf(c) >= 0) { if (seenRadixPoint) throw invalidFormat(s, si, ei); seenRadixPoint = true; @@ -1495,8 +1495,8 @@ public static long parse(final CharSequence s, final int si, final int ei, final boolean roundedUp = false, rounded = false, midpoint = false; int additionalExponent = 0; - while ((c >= '0' && c <= '9') || c == '.') { - if (c == '.') { + while ((c >= '0' && c <= '9') || decimalMarks.indexOf(c) >= 0) { + if (decimalMarks.indexOf(c) >= 0) { if (seenRadixPoint) throw invalidFormat(s, si, ei); diff --git a/java/dfp/src/main/java/com/epam/deltix/dfp/JavaImplParse.java b/java/dfp/src/main/java/com/epam/deltix/dfp/JavaImplParse.java index 8fab574f..951d56e2 100644 --- a/java/dfp/src/main/java/com/epam/deltix/dfp/JavaImplParse.java +++ b/java/dfp/src/main/java/com/epam/deltix/dfp/JavaImplParse.java @@ -59,7 +59,7 @@ static class FloatingPointStatusFlag { } //public static long parse(final CharSequence s, final int si, final int ei, final int roundingMode) - public static long bid64_from_string(final CharSequence s, int si, int ei, final FloatingPointStatusFlag pfpsf, int rnd_mode /*= BID_ROUNDING_TO_NEAREST*/) { + public static long bid64_from_string(final CharSequence s, int si, int ei, final FloatingPointStatusFlag pfpsf, int rnd_mode /*= BID_ROUNDING_TO_NEAREST*/, final String decimalMarks) { long coefficient_x = 0, rounded = 0; int expon_x = 0, sgn_expon, ndigits, add_expon = 0, midpoint = 0, rounded_up = 0; @@ -102,8 +102,10 @@ public static long bid64_from_string(final CharSequence s, int si, int ei, final } } + boolean cEqDot = decimalMarks.indexOf(c) >= 0; + // detect special cases (INF or NaN) - if (c != '.' && (c < '0' || c > '9')) { + if (!cEqDot && (c < '0' || c > '9')) { if (IsStrEqIgnoreCase(s, ps, ei, "inf") || IsStrEqIgnoreCase(s, ps, ei, "infinity")) // Infinity? { pfpsf.status = BID_EXACT_STATUS; @@ -130,9 +132,9 @@ public static long bid64_from_string(final CharSequence s, int si, int ei, final int right_radix_leading_zeros = 0; // detect zero (and eliminate/ignore leading zeros) - if (c == '0' || c == '.') { + if (c == '0' || cEqDot) { - if (c == '.') { + if (cEqDot) { rdx_pt_enc = 1; ps++; c = ps < ei ? s.charAt(ps) : '\0'; @@ -149,7 +151,7 @@ public static long bid64_from_string(final CharSequence s, int si, int ei, final } // if this character is a radix point, make sure we haven't already // encountered one - if (c == '.') { + if (decimalMarks.indexOf(c) >= 0) { if (rdx_pt_enc == 0) { rdx_pt_enc = 1; // if this is the first radix point, and the next character is NULL, @@ -175,8 +177,69 @@ public static long bid64_from_string(final CharSequence s, int si, int ei, final c = ps < ei ? s.charAt(ps) : '\0'; ndigits = 0; - while ((c >= '0' && c <= '9') || c == '.') { - if (c == '.') { + while ((c >= '0' && c <= '9') || (decimalMarks.indexOf(c) >= 0)) { + if (c >= '0' && c <= '9') { + dec_expon_scale += rdx_pt_enc; + + ndigits++; + if (ndigits <= 16) { + coefficient_x = (coefficient_x << 1) + (coefficient_x << 3); + coefficient_x += (c - '0'); + } else if (ndigits == 17) { + // coefficient rounding + switch (rnd_mode) { + case BID_ROUNDING_TO_NEAREST: + midpoint = (c == '5' && (coefficient_x & 1) == 0) ? 1 : 0; + // if coefficient is even and c is 5, prepare to round up if + // subsequent digit is nonzero + // if str[MAXDIG+1] > 5, we MUST round up + // if str[MAXDIG+1] == 5 and coefficient is ODD, ROUND UP! + if (c > '5' || (c == '5' && (coefficient_x & 1) != 0)) { + coefficient_x++; + rounded_up = 1; + } + break; + + case BID_ROUNDING_DOWN: + if (sign_x != 0) { + coefficient_x++; + rounded_up = 1; + } + break; + case BID_ROUNDING_UP: + if (sign_x == 0) { + coefficient_x++; + rounded_up = 1; + } + break; + case BID_ROUNDING_TIES_AWAY: + if (c >= '5') { + coefficient_x++; + rounded_up = 1; + } + break; + } + if (coefficient_x == 10000000000000000L) { + coefficient_x = 1000000000000000L; + add_expon = 1; + } + if (c > '0') + rounded = 1; + add_expon += 1; + } else { // ndigits > 17 + add_expon++; + if (midpoint != 0 && c > '0') { + coefficient_x++; + midpoint = 0; + rounded_up = 1; + } + if (c > '0') + rounded = 1; + } + ps++; + c = ps < ei ? s.charAt(ps) : '\0'; + + } else { // decimalMarks.indexOf(c) >= 0 if (rdx_pt_enc != 0) { pfpsf.status = BID_INVALID_FORMAT; return NaN; // 0x7c00000000000000L | sign_x; // return NaN @@ -184,67 +247,7 @@ public static long bid64_from_string(final CharSequence s, int si, int ei, final rdx_pt_enc = 1; ps++; c = ps < ei ? s.charAt(ps) : '\0'; - continue; } - dec_expon_scale += rdx_pt_enc; - - ndigits++; - if (ndigits <= 16) { - coefficient_x = (coefficient_x << 1) + (coefficient_x << 3); - coefficient_x += (c - '0'); - } else if (ndigits == 17) { - // coefficient rounding - switch (rnd_mode) { - case BID_ROUNDING_TO_NEAREST: - midpoint = (c == '5' && (coefficient_x & 1) == 0) ? 1 : 0; - // if coefficient is even and c is 5, prepare to round up if - // subsequent digit is nonzero - // if str[MAXDIG+1] > 5, we MUST round up - // if str[MAXDIG+1] == 5 and coefficient is ODD, ROUND UP! - if (c > '5' || (c == '5' && (coefficient_x & 1) != 0)) { - coefficient_x++; - rounded_up = 1; - } - break; - - case BID_ROUNDING_DOWN: - if (sign_x != 0) { - coefficient_x++; - rounded_up = 1; - } - break; - case BID_ROUNDING_UP: - if (sign_x == 0) { - coefficient_x++; - rounded_up = 1; - } - break; - case BID_ROUNDING_TIES_AWAY: - if (c >= '5') { - coefficient_x++; - rounded_up = 1; - } - break; - } - if (coefficient_x == 10000000000000000L) { - coefficient_x = 1000000000000000L; - add_expon = 1; - } - if (c > '0') - rounded = 1; - add_expon += 1; - } else { // ndigits > 17 - add_expon++; - if (midpoint != 0 && c > '0') { - coefficient_x++; - midpoint = 0; - rounded_up = 1; - } - if (c > '0') - rounded = 1; - } - ps++; - c = ps < ei ? s.charAt(ps) : '\0'; } add_expon -= (dec_expon_scale + right_radix_leading_zeros); diff --git a/java/dfp/src/test/java/com/epam/deltix/dfp/JavaImplTest.java b/java/dfp/src/test/java/com/epam/deltix/dfp/JavaImplTest.java index 17202dc7..86bd64ae 100644 --- a/java/dfp/src/test/java/com/epam/deltix/dfp/JavaImplTest.java +++ b/java/dfp/src/test/java/com/epam/deltix/dfp/JavaImplTest.java @@ -15,6 +15,7 @@ import java.security.SecureRandom; import java.util.Random; +import static com.epam.deltix.dfp.Decimal64Utils.*; import static com.epam.deltix.dfp.JavaImpl.*; import static com.epam.deltix.dfp.JavaImplAdd.LONG_LOW_PART; import static com.epam.deltix.dfp.JavaImplCmp.MASK_BINARY_SIG2; @@ -122,25 +123,25 @@ public void appendTo() throws IOException { final StringBuilder string = new StringBuilder(); string.setLength(0); - assertEquals("NaN", appendToRefImpl(Decimal64Utils.NaN, string).toString()); + assertEquals("NaN", appendToRefImpl(Decimal64Utils.NaN, DECIMAL_MARK_DOT, string).toString()); string.setLength(0); - assertEquals("Infinity", appendToRefImpl(Decimal64Utils.POSITIVE_INFINITY, string).toString()); + assertEquals("Infinity", appendToRefImpl(Decimal64Utils.POSITIVE_INFINITY, DECIMAL_MARK_DOT, string).toString()); string.setLength(0); - assertEquals("-Infinity", appendToRefImpl(Decimal64Utils.NEGATIVE_INFINITY, string).toString()); + assertEquals("-Infinity", appendToRefImpl(Decimal64Utils.NEGATIVE_INFINITY, DECIMAL_MARK_DOT, string).toString()); string.setLength(0); - assertEquals("100000010000.0", JavaImpl.appendToRefImpl(Decimal64Utils.fromDouble(10000001E+04), string).toString()); + assertEquals("100000010000.0", JavaImpl.appendToRefImpl(Decimal64Utils.fromDouble(10000001E+04), DECIMAL_MARK_DOT, string).toString()); string.setLength(0); - assertEquals("10000001.0", JavaImpl.appendToRefImpl(Decimal64Utils.fromDouble(10000001), string).toString()); + assertEquals("10000001.0", JavaImpl.appendToRefImpl(Decimal64Utils.fromDouble(10000001), DECIMAL_MARK_DOT, string).toString()); string.setLength(0); - assertEquals("1000.0001", JavaImpl.appendToRefImpl(Decimal64Utils.fromDouble(10000001E-04), string).toString()); + assertEquals("1000.0001", JavaImpl.appendToRefImpl(Decimal64Utils.fromDouble(10000001E-04), DECIMAL_MARK_DOT, string).toString()); string.setLength(0); - assertEquals("9.2", appendToRefImpl(Decimal64Utils.fromDecimalDouble(92E-01), string).toString()); + assertEquals("9.2", appendToRefImpl(Decimal64Utils.fromDecimalDouble(92E-01), DECIMAL_MARK_DOT, string).toString()); } @Test @@ -231,12 +232,12 @@ public void toStringSpecialCase4() { @Test(expected = NumberFormatException.class) public void parseEmptyString() { - JavaImpl.parse("asdf", 0, 0, BID_ROUNDING_TO_NEAREST); + Decimal64Utils.parse("asdf", 0, 0); } @Test(expected = NumberFormatException.class) public void parseNonDigits() { - JavaImpl.parse("asdf", 0, 4, BID_ROUNDING_TO_NEAREST); + Decimal64Utils.parse("asdf", 0, 4); } @Test @@ -527,7 +528,7 @@ public void testToStringRandomly() throws Exception { private static void checkStrEq(final long value) { try { - final String inStr = JavaImpl.appendToRefImpl(value, new StringBuilder()).toString(); + final String inStr = JavaImpl.appendToRefImpl(value, DECIMAL_MARK_DEFAULT, new StringBuilder()).toString(); final String testStr = Decimal64Utils.toString(value); if (!inStr.equals(testStr)) throw new RuntimeException("Case toString(" + value + "L) error: ref toString(=" + inStr + ") != test toString(=" + testStr + ")"); @@ -644,7 +645,7 @@ public void TestParseReImpl() { { final String testStr = " 000 "; JavaImplParse.FloatingPointStatusFlag fpsf = new JavaImplParse.FloatingPointStatusFlag(); - Decimal64 value = Decimal64.fromUnderlying(JavaImplParse.bid64_from_string(testStr, 0, testStr.length(), fpsf, roundMode)); + Decimal64 value = Decimal64.fromUnderlying(JavaImplParse.bid64_from_string(testStr, 0, testStr.length(), fpsf, roundMode, DECIMAL_MARK_ANY)); DoubleHolder doubleHolder = new DoubleHolder(); final boolean doubleParseOk = doubleTryParse(testStr, doubleHolder); assertEquals(Decimal64.ZERO, value); @@ -654,7 +655,7 @@ public void TestParseReImpl() { { final String testStr = "00.."; JavaImplParse.FloatingPointStatusFlag fpsf = new JavaImplParse.FloatingPointStatusFlag(); - Decimal64 value = Decimal64.fromUnderlying(JavaImplParse.bid64_from_string(testStr, 0, testStr.length(), fpsf, roundMode)); + Decimal64 value = Decimal64.fromUnderlying(JavaImplParse.bid64_from_string(testStr, 0, testStr.length(), fpsf, roundMode, DECIMAL_MARK_ANY)); DoubleHolder doubleHolder = new DoubleHolder(); final boolean doubleParseOk = doubleTryParse(testStr, doubleHolder); assertEquals(Decimal64.NaN, value); @@ -664,7 +665,7 @@ public void TestParseReImpl() { { final String testStr = "000235"; JavaImplParse.FloatingPointStatusFlag fpsf = new JavaImplParse.FloatingPointStatusFlag(); - Decimal64 value = Decimal64.fromUnderlying(JavaImplParse.bid64_from_string(testStr, 0, testStr.length(), fpsf, roundMode)); + Decimal64 value = Decimal64.fromUnderlying(JavaImplParse.bid64_from_string(testStr, 0, testStr.length(), fpsf, roundMode, DECIMAL_MARK_ANY)); DoubleHolder doubleHolder = new DoubleHolder(); final boolean doubleParseOk = doubleTryParse(testStr, doubleHolder); assertEquals(Decimal64.fromInt(235), value); @@ -674,7 +675,7 @@ public void TestParseReImpl() { { final String testStr = "00.0000235"; JavaImplParse.FloatingPointStatusFlag fpsf = new JavaImplParse.FloatingPointStatusFlag(); - Decimal64 value = Decimal64.fromUnderlying(JavaImplParse.bid64_from_string(testStr, 0, testStr.length(), fpsf, roundMode)); + Decimal64 value = Decimal64.fromUnderlying(JavaImplParse.bid64_from_string(testStr, 0, testStr.length(), fpsf, roundMode, DECIMAL_MARK_ANY)); DoubleHolder doubleHolder = new DoubleHolder(); final boolean doubleParseOk = doubleTryParse(testStr, doubleHolder); assertEquals(Decimal64.fromFixedPoint(235, 7), value); @@ -684,7 +685,7 @@ public void TestParseReImpl() { { final String testStr = "1234512345123451234500000"; JavaImplParse.FloatingPointStatusFlag fpsf = new JavaImplParse.FloatingPointStatusFlag(); - Decimal64 value = Decimal64.fromUnderlying(JavaImplParse.bid64_from_string(testStr, 0, testStr.length(), fpsf, roundMode)); + Decimal64 value = Decimal64.fromUnderlying(JavaImplParse.bid64_from_string(testStr, 0, testStr.length(), fpsf, roundMode, DECIMAL_MARK_ANY)); DoubleHolder doubleHolder = new DoubleHolder(); final boolean doubleParseOk = doubleTryParse(testStr, doubleHolder); assertEquals(Decimal64.fromFixedPoint(1234512345123451L, -9), value); @@ -694,7 +695,7 @@ public void TestParseReImpl() { { final String testStr = "1234512345123451234500000e+12345123451234512345"; JavaImplParse.FloatingPointStatusFlag fpsf = new JavaImplParse.FloatingPointStatusFlag(); - Decimal64 value = Decimal64.fromUnderlying(JavaImplParse.bid64_from_string(testStr, 0, testStr.length(), fpsf, roundMode)); + Decimal64 value = Decimal64.fromUnderlying(JavaImplParse.bid64_from_string(testStr, 0, testStr.length(), fpsf, roundMode, DECIMAL_MARK_ANY)); DoubleHolder doubleHolder = new DoubleHolder(); final boolean doubleParseOk = doubleTryParse(testStr, doubleHolder); assertEquals(Decimal64.POSITIVE_INFINITY, value); @@ -704,7 +705,7 @@ public void TestParseReImpl() { { final String testStr = " -5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 "; JavaImplParse.FloatingPointStatusFlag fpsf = new JavaImplParse.FloatingPointStatusFlag(); - Decimal64 value = Decimal64.fromUnderlying(JavaImplParse.bid64_from_string(testStr, 0, testStr.length(), fpsf, roundMode)); + Decimal64 value = Decimal64.fromUnderlying(JavaImplParse.bid64_from_string(testStr, 0, testStr.length(), fpsf, roundMode, DECIMAL_MARK_ANY)); DoubleHolder doubleHolder = new DoubleHolder(); final boolean doubleParseOk = doubleTryParse(testStr, doubleHolder); assertEquals(Decimal64.NEGATIVE_INFINITY, value); @@ -714,7 +715,7 @@ public void TestParseReImpl() { { final String testStr = "0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005"; JavaImplParse.FloatingPointStatusFlag fpsf = new JavaImplParse.FloatingPointStatusFlag(); - Decimal64 value = Decimal64.fromUnderlying(JavaImplParse.bid64_from_string(testStr, 0, testStr.length(), fpsf, roundMode)); + Decimal64 value = Decimal64.fromUnderlying(JavaImplParse.bid64_from_string(testStr, 0, testStr.length(), fpsf, roundMode, DECIMAL_MARK_ANY)); DoubleHolder doubleHolder = new DoubleHolder(); final boolean doubleParseOk = doubleTryParse(testStr, doubleHolder); assertEquals(Decimal64.ZERO, value); @@ -724,7 +725,7 @@ public void TestParseReImpl() { { final String testStr = " 123 x99 "; JavaImplParse.FloatingPointStatusFlag fpsf = new JavaImplParse.FloatingPointStatusFlag(); - Decimal64 value = Decimal64.fromUnderlying(JavaImplParse.bid64_from_string(testStr, 0, testStr.length(), fpsf, roundMode)); + Decimal64 value = Decimal64.fromUnderlying(JavaImplParse.bid64_from_string(testStr, 0, testStr.length(), fpsf, roundMode, DECIMAL_MARK_ANY)); DoubleHolder doubleHolder = new DoubleHolder(); final boolean doubleParseOk = doubleTryParse(testStr, doubleHolder); assertEquals(Decimal64.NaN, value); diff --git a/java/dfpNativeTests/src/jmh/java/com/epam/deltix/dfp/FormattingBenchmark.java b/java/dfpNativeTests/src/jmh/java/com/epam/deltix/dfp/FormattingBenchmark.java index 40735e5e..025a8553 100644 --- a/java/dfpNativeTests/src/jmh/java/com/epam/deltix/dfp/FormattingBenchmark.java +++ b/java/dfpNativeTests/src/jmh/java/com/epam/deltix/dfp/FormattingBenchmark.java @@ -11,6 +11,7 @@ import java.util.Random; import java.util.concurrent.TimeUnit; +import static com.epam.deltix.dfp.Decimal64Utils.*; import static com.epam.deltix.dfp.MathBenchmark.fixedSeed; @BenchmarkMode(Mode.AverageTime) @@ -38,26 +39,26 @@ public void setUp() { @Benchmark public Appendable appendToRefImplRet() throws IOException { stringBuilder.setLength(0); - JavaImpl.appendToRefImpl(decimalValue, stringBuilder); + JavaImpl.appendToRefImpl(decimalValue, DECIMAL_MARK_DEFAULT, stringBuilder); return stringBuilder; } @Benchmark public void appendToRefImplBlackHole(Blackhole bh) throws IOException { stringBuilder.setLength(0); - bh.consume(JavaImpl.appendToRefImpl(decimalValue, stringBuilder)); + bh.consume(JavaImpl.appendToRefImpl(decimalValue, DECIMAL_MARK_DEFAULT, stringBuilder)); } @Benchmark public void fastAppendToAppendable(Blackhole bh) throws IOException { stringBuilder.setLength(0); - bh.consume(JavaImpl.fastAppendToAppendable(decimalValue, stringBuilder)); + bh.consume(JavaImpl.fastAppendToAppendable(decimalValue, DECIMAL_MARK_DEFAULT, stringBuilder)); } @Benchmark public void fastAppendToStringBuilder(Blackhole bh) { stringBuilder.setLength(0); - bh.consume(JavaImpl.fastAppendToStringBuilder(decimalValue, stringBuilder)); + bh.consume(JavaImpl.fastAppendToStringBuilder(decimalValue, DECIMAL_MARK_DEFAULT, stringBuilder)); } @Benchmark @@ -82,7 +83,7 @@ public void appendJustDouble(Blackhole bh) { public void toStringJavaImpl(Blackhole bh) { for (int i = 0; i < decimalValues.length; ++i) { try { - bh.consume(JavaImpl.appendToRefImpl(decimalValues[i], new StringBuilder()).toString()); + bh.consume(JavaImpl.appendToRefImpl(decimalValues[i], DECIMAL_MARK_DEFAULT, new StringBuilder()).toString()); } catch (final Exception e) { throw new RuntimeException(e); } @@ -92,13 +93,13 @@ public void toStringJavaImpl(Blackhole bh) { @Benchmark public void toStringJavaFastImpl(Blackhole bh) { for (int i = 0; i < decimalValues.length; ++i) - bh.consume(JavaImpl.fastToString(decimalValues[i])); + bh.consume(JavaImpl.fastToString(decimalValues[i], DECIMAL_MARK_DEFAULT)); } @Benchmark public void toScientificStringJavaFastImpl(Blackhole bh) { for (int i = 0; i < decimalValues.length; ++i) - bh.consume(JavaImpl.fastToScientificString(decimalValues[i])); + bh.consume(JavaImpl.fastToScientificString(decimalValues[i], DECIMAL_MARK_DEFAULT)); } public static void main(String[] args) throws RunnerException { From e5e77e02f2a8f8bf74d084c51bd067c62bd08c8b Mon Sep 17 00:00:00 2001 From: Andrei Davydov Date: Tue, 19 Sep 2023 22:23:20 +0300 Subject: [PATCH 3/5] .NET: Support custom decimal separators for ToString() and similar methods. --- csharp/EPAM.Deltix.DFP/Decimal64.cs | 33 +++++- csharp/EPAM.Deltix.DFP/DotNetImpl.cs | 30 +++--- csharp/EPAM.Deltix.DFP/DotNetReImpl.cs | 136 +++++++++++++------------ 3 files changed, 114 insertions(+), 85 deletions(-) diff --git a/csharp/EPAM.Deltix.DFP/Decimal64.cs b/csharp/EPAM.Deltix.DFP/Decimal64.cs index f2581a39..81a2a8a3 100644 --- a/csharp/EPAM.Deltix.DFP/Decimal64.cs +++ b/csharp/EPAM.Deltix.DFP/Decimal64.cs @@ -40,6 +40,11 @@ public struct Decimal64 : IComparable, IEquatable, ISerial public static readonly int MaxExponent = 384; public static readonly int MinExponent = -383; + public const char DecimalMarkDot = '.'; + public const char DecimalMarkComma = ','; + public const char DecimalMarkDefault = DecimalMarkDot; + public static string DecimalMarkAny = "" + DecimalMarkDot + DecimalMarkComma; + #endregion public UInt64 Bits { get; } @@ -53,7 +58,12 @@ internal Decimal64(UInt64 value) public override String ToString() { - return DotNetImpl.ToString(Bits); + return DotNetImpl.ToString(Bits, DecimalMarkDefault); + } + + public String ToString(char decimalMark) + { + return DotNetImpl.ToString(Bits, decimalMark); //return ((Double)this).ToString(CultureInfo.InvariantCulture); } @@ -986,17 +996,32 @@ public static Boolean TryParse(String text, out Decimal64 result) public String ToScientificString() { - return DotNetImpl.ToScientificString(Bits); + return DotNetImpl.ToScientificString(Bits, DecimalMarkDefault); + } + + public String ToScientificString(char decimalMark) + { + return DotNetImpl.ToScientificString(Bits, decimalMark); } public StringBuilder AppendTo(StringBuilder text) { - return DotNetImpl.AppendTo(Bits, text); + return DotNetImpl.AppendTo(Bits, DecimalMarkDefault, text); + } + + public StringBuilder AppendTo(char decimalMark, StringBuilder text) + { + return DotNetImpl.AppendTo(Bits, decimalMark, text); } public StringBuilder ScientificAppendTo(StringBuilder text) { - return DotNetImpl.ScientificAppendTo(Bits, text); + return DotNetImpl.ScientificAppendTo(Bits, DecimalMarkDefault, text); + } + + public StringBuilder ScientificAppendTo(char decimalMark, StringBuilder text) + { + return DotNetImpl.ScientificAppendTo(Bits, decimalMark, text); } #endregion diff --git a/csharp/EPAM.Deltix.DFP/DotNetImpl.cs b/csharp/EPAM.Deltix.DFP/DotNetImpl.cs index 65bbf38e..33bc5b35 100644 --- a/csharp/EPAM.Deltix.DFP/DotNetImpl.cs +++ b/csharp/EPAM.Deltix.DFP/DotNetImpl.cs @@ -229,7 +229,7 @@ public static UInt64 FromDecimal(Decimal dec) { DecimalNet* decPtr = (DecimalNet*)&dec; ulong sign = ((ulong)decPtr->flags & 0x80000000UL) << 32; - int exp = -((int)(decPtr ->flags >> 16) & 0xFF); + int exp = -((int)(decPtr->flags >> 16) & 0xFF); ulong mantissa; if (decPtr->hi == 0) { @@ -1158,7 +1158,7 @@ static bool IsRoundedToReciprocalImpl(int addExponent, Pair96 coefficientMulR, F #endregion #region Formatting & Parsing - public static String ToString(UInt64 value) + public static String ToString(UInt64 value, char decimalMark) { if (!IsFinite(value)) { @@ -1234,7 +1234,7 @@ public static String ToString(UInt64 value) { Int64 old = coefficient + '0'; coefficient /= 10; - *p = '.'; + *p = decimalMark; p += 0 == dotPos-- ? -1 : 0; // Hopefully branch-free method to insert decimal dot *p-- = (char)(old - coefficient * 10); // = [old - new * 10] } while (coefficient != 0); @@ -1243,7 +1243,7 @@ public static String ToString(UInt64 value) { for (; dotPos > 0; --dotPos) *p-- = '0'; - p[0] = '.'; + p[0] = decimalMark; p[-1] = '0'; p -= 2; } @@ -1256,7 +1256,7 @@ public static String ToString(UInt64 value) if ('0' == *e) { while ('0' == *--e) { } - if ('.' == *e) + if (decimalMark == *e) --e; } } @@ -1267,7 +1267,7 @@ public static String ToString(UInt64 value) } } - public static string ToScientificString(UInt64 value) + public static string ToScientificString(UInt64 value, char decimalMark) { if (IsNull(value)) return "null"; @@ -1285,7 +1285,7 @@ public static string ToScientificString(UInt64 value) UInt64 coefficient = Unpack(value, out sign, out exponent); if (coefficient == 0) - return "0.000000000000000e+000"; + return "0" + decimalMark + "000000000000000e+000"; exponent -= BaseExponent; @@ -1308,7 +1308,7 @@ public static string ToScientificString(UInt64 value) bi--; *bi = *(bi + 1); - *(bi + 1) = '.'; + *(bi + 1) = decimalMark; if (sign) *--bi = '-'; @@ -1329,7 +1329,7 @@ public static string ToScientificString(UInt64 value) } } - public static StringBuilder AppendTo(UInt64 value, StringBuilder text) + public static StringBuilder AppendTo(UInt64 value, char decimalMark, StringBuilder text) { if (!IsFinite(value)) { @@ -1411,7 +1411,7 @@ public static StringBuilder AppendTo(UInt64 value, StringBuilder text) { Int64 old = coefficient + '0'; coefficient /= 10; - *p = '.'; + *p = decimalMark; p += 0 == dotPos-- ? -1 : 0; // Hopefully branch-free method to insert decimal dot *p-- = (char)(old - coefficient * 10); // = [old - new * 10] } while (coefficient != 0); @@ -1420,7 +1420,7 @@ public static StringBuilder AppendTo(UInt64 value, StringBuilder text) { for (; dotPos > 0; --dotPos) *p-- = '0'; - p[0] = '.'; + p[0] = decimalMark; p[-1] = '0'; p -= 2; } @@ -1433,7 +1433,7 @@ public static StringBuilder AppendTo(UInt64 value, StringBuilder text) if ('0' == *e) { while ('0' == *--e) { } - if ('.' == *e) + if (decimalMark == *e) --e; } } @@ -1450,7 +1450,7 @@ public static StringBuilder AppendTo(UInt64 value, StringBuilder text) } } - public static StringBuilder ScientificAppendTo(UInt64 value, StringBuilder text) + public static StringBuilder ScientificAppendTo(UInt64 value, char decimalMark, StringBuilder text) { if (IsNull(value)) return text.Append("null"); @@ -1467,7 +1467,7 @@ public static StringBuilder ScientificAppendTo(UInt64 value, StringBuilder text) UInt64 coefficient = Unpack(value, out sign, out exponent); if (coefficient == 0) - return text.Append("0.000000000000000e+000"); + return text.Append('0').Append(decimalMark).Append("000000000000000e+000"); exponent -= BaseExponent; @@ -1490,7 +1490,7 @@ public static StringBuilder ScientificAppendTo(UInt64 value, StringBuilder text) bi--; *bi = *(bi + 1); - *(bi + 1) = '.'; + *(bi + 1) = decimalMark; if (sign) *--bi = '-'; diff --git a/csharp/EPAM.Deltix.DFP/DotNetReImpl.cs b/csharp/EPAM.Deltix.DFP/DotNetReImpl.cs index 6fac974e..0ccdd9bf 100644 --- a/csharp/EPAM.Deltix.DFP/DotNetReImpl.cs +++ b/csharp/EPAM.Deltix.DFP/DotNetReImpl.cs @@ -76,7 +76,7 @@ private unsafe static bool IsStrEq(char* ptr, string str) } } - public unsafe static BID_UINT64 bid64_from_string(string s, out _IDEC_flags pfpsf, int rnd_mode = BID_ROUNDING_TO_NEAREST/*, _EXC_MASKS_PARAM _EXC_INFO_PARAM*/) + public unsafe static BID_UINT64 bid64_from_string(string s, out _IDEC_flags pfpsf, string decimalMarks, int rnd_mode = BID_ROUNDING_TO_NEAREST/*, _EXC_MASKS_PARAM _EXC_INFO_PARAM*/) { BID_UINT64 coefficient_x = 0, rounded = 0; @@ -107,8 +107,10 @@ public unsafe static BID_UINT64 bid64_from_string(string s, out _IDEC_flags pfps } } + bool cEqDot = decimalMarks.IndexOf(*ps) >= 0; + // detect special cases (INF or NaN) - if (*ps != '.' && (*ps < '0' || *ps > '9')) + if (cEqDot && (*ps < '0' || *ps > '9')) { if (IsStrEq(ps, "inf") || IsStrEq(ps, "infinity")) // Infinity? { @@ -137,10 +139,10 @@ public unsafe static BID_UINT64 bid64_from_string(string s, out _IDEC_flags pfps int right_radix_leading_zeros = 0; // detect zero (and eliminate/ignore leading zeros) - if (*ps == '0' || *ps == '.') + if (*ps == '0' || cEqDot) { - if (*ps == '.') + if (cEqDot) { rdx_pt_enc = 1; ps++; @@ -158,7 +160,7 @@ public unsafe static BID_UINT64 bid64_from_string(string s, out _IDEC_flags pfps } // if this character is a radix point, make sure we haven't already // encountered one - if (*ps == '.') + if (decimalMarks.IndexOf(*ps) >= 0) { if (rdx_pt_enc == 0) { @@ -189,79 +191,81 @@ public unsafe static BID_UINT64 bid64_from_string(string s, out _IDEC_flags pfps char c = *ps; ndigits = 0; - while ((c >= '0' && c <= '9') || c == '.') + while ((c >= '0' && c <= '9') || (decimalMarks.IndexOf(c) >= 0)) { - if (c == '.') + if (c >= '0' && c <= '9') { - if (rdx_pt_enc != 0) - { - pfpsf = BID_INVALID_FORMAT; - return DotNetImpl.NaN; // 0x7c00000000000000UL | sign_x; // return NaN - } - rdx_pt_enc = 1; - ps++; - c = *ps; - continue; - } - dec_expon_scale += rdx_pt_enc; + dec_expon_scale += rdx_pt_enc; - ndigits++; - if (ndigits <= 16) - { - coefficient_x = (coefficient_x << 1) + (coefficient_x << 3); - coefficient_x += (BID_UINT64)(c - '0'); - } - else if (ndigits == 17) - { - // coefficient rounding - switch (rnd_mode) + ndigits++; + if (ndigits <= 16) { - case BID_ROUNDING_TO_NEAREST: - midpoint = (c == '5' && (coefficient_x & 1) == 0) ? 1 : 0; - // if coefficient is even and c is 5, prepare to round up if - // subsequent digit is nonzero - // if str[MAXDIG+1] > 5, we MUST round up - // if str[MAXDIG+1] == 5 and coefficient is ODD, ROUND UP! - if (c > '5' || (c == '5' && (coefficient_x & 1) != 0)) - { - coefficient_x++; - rounded_up = 1; - } - break; - - case BID_ROUNDING_DOWN: - if (sign_x != 0) { coefficient_x++; rounded_up = 1; } - break; - case BID_ROUNDING_UP: - if (sign_x == 0) { coefficient_x++; rounded_up = 1; } - break; - case BID_ROUNDING_TIES_AWAY: - if (c >= '5') { coefficient_x++; rounded_up = 1; } - break; + coefficient_x = (coefficient_x << 1) + (coefficient_x << 3); + coefficient_x += (BID_UINT64)(c - '0'); } - if (coefficient_x == 10000000000000000UL) + else if (ndigits == 17) { - coefficient_x = 1000000000000000UL; - add_expon = 1; + // coefficient rounding + switch (rnd_mode) + { + case BID_ROUNDING_TO_NEAREST: + midpoint = (c == '5' && (coefficient_x & 1) == 0) ? 1 : 0; + // if coefficient is even and c is 5, prepare to round up if + // subsequent digit is nonzero + // if str[MAXDIG+1] > 5, we MUST round up + // if str[MAXDIG+1] == 5 and coefficient is ODD, ROUND UP! + if (c > '5' || (c == '5' && (coefficient_x & 1) != 0)) + { + coefficient_x++; + rounded_up = 1; + } + break; + + case BID_ROUNDING_DOWN: + if (sign_x != 0) { coefficient_x++; rounded_up = 1; } + break; + case BID_ROUNDING_UP: + if (sign_x == 0) { coefficient_x++; rounded_up = 1; } + break; + case BID_ROUNDING_TIES_AWAY: + if (c >= '5') { coefficient_x++; rounded_up = 1; } + break; + } + if (coefficient_x == 10000000000000000UL) + { + coefficient_x = 1000000000000000UL; + add_expon = 1; + } + if (c > '0') + rounded = 1; + add_expon += 1; } - if (c > '0') - rounded = 1; - add_expon += 1; + else + { // ndigits > 17 + add_expon++; + if (midpoint != 0 && c > '0') + { + coefficient_x++; + midpoint = 0; + rounded_up = 1; + } + if (c > '0') + rounded = 1; + } + ps++; + c = *ps; } else - { // ndigits > 17 - add_expon++; - if (midpoint != 0 && c > '0') + { + if (rdx_pt_enc != 0) { - coefficient_x++; - midpoint = 0; - rounded_up = 1; + pfpsf = BID_INVALID_FORMAT; + return DotNetImpl.NaN; // 0x7c00000000000000UL | sign_x; // return NaN } - if (c > '0') - rounded = 1; + rdx_pt_enc = 1; + ps++; + c = *ps; } - ps++; - c = *ps; } add_expon -= (dec_expon_scale + right_radix_leading_zeros); From 89b1444c92bda8debd4856f1bd54cbfded2099e3 Mon Sep 17 00:00:00 2001 From: Andrei Davydov Date: Wed, 20 Sep 2023 11:13:06 +0300 Subject: [PATCH 4/5] .NET: Fix compilation --- csharp/EPAM.Deltix.DFP.Test/Decimal64Test.cs | 18 +++++----- csharp/EPAM.Deltix.DFP/Decimal64.cs | 38 ++++++++++++++++++-- csharp/EPAM.Deltix.DFP/DotNetReImpl.cs | 2 +- 3 files changed, 45 insertions(+), 13 deletions(-) diff --git a/csharp/EPAM.Deltix.DFP.Test/Decimal64Test.cs b/csharp/EPAM.Deltix.DFP.Test/Decimal64Test.cs index 5a4d80ba..94067e98 100644 --- a/csharp/EPAM.Deltix.DFP.Test/Decimal64Test.cs +++ b/csharp/EPAM.Deltix.DFP.Test/Decimal64Test.cs @@ -706,7 +706,7 @@ public void TestParseReImpl() { var testStr = "000"; uint fpsf; - var value = Decimal64.FromUnderlying(DotNetReImpl.bid64_from_string(testStr, out fpsf, roundMode)); + var value = Decimal64.FromUnderlying(DotNetReImpl.bid64_from_string(testStr, Decimal64.DecimalMarkAny, out fpsf, roundMode)); double doubleValue; var doubleParseOk = Double.TryParse(testStr, NumberStyles.Float, CultureInfo.InvariantCulture, out doubleValue); Assert.AreEqual(Decimal64.Zero, value); @@ -716,7 +716,7 @@ public void TestParseReImpl() { var testStr = "00.."; uint fpsf; - var value = Decimal64.FromUnderlying(DotNetReImpl.bid64_from_string(testStr, out fpsf, roundMode)); + var value = Decimal64.FromUnderlying(DotNetReImpl.bid64_from_string(testStr, Decimal64.DecimalMarkAny, out fpsf, roundMode)); double doubleValue; var doubleParseOk = Double.TryParse(testStr, NumberStyles.Float, CultureInfo.InvariantCulture, out doubleValue); Assert.AreEqual(Decimal64.NaN, value); @@ -726,7 +726,7 @@ public void TestParseReImpl() { var testStr = "000235"; uint fpsf; - var value = Decimal64.FromUnderlying(DotNetReImpl.bid64_from_string(testStr, out fpsf, roundMode)); + var value = Decimal64.FromUnderlying(DotNetReImpl.bid64_from_string(testStr, Decimal64.DecimalMarkAny, out fpsf, roundMode)); double doubleValue; var doubleParseOk = Double.TryParse(testStr, NumberStyles.Float, CultureInfo.InvariantCulture, out doubleValue); Assert.AreEqual(Decimal64.FromInt(235), value); @@ -736,7 +736,7 @@ public void TestParseReImpl() { var testStr = "00.0000235"; uint fpsf; - var value = Decimal64.FromUnderlying(DotNetReImpl.bid64_from_string(testStr, out fpsf, roundMode)); + var value = Decimal64.FromUnderlying(DotNetReImpl.bid64_from_string(testStr, Decimal64.DecimalMarkAny, out fpsf, roundMode)); double doubleValue; var doubleParseOk = Double.TryParse(testStr, NumberStyles.Float, CultureInfo.InvariantCulture, out doubleValue); Assert.AreEqual(Decimal64.FromFixedPoint(235, 7), value); @@ -746,7 +746,7 @@ public void TestParseReImpl() { var testStr = "1234512345123451234500000"; uint fpsf; - var value = Decimal64.FromUnderlying(DotNetReImpl.bid64_from_string(testStr, out fpsf, roundMode)); + var value = Decimal64.FromUnderlying(DotNetReImpl.bid64_from_string(testStr, Decimal64.DecimalMarkAny, out fpsf, roundMode)); double doubleValue; var doubleParseOk = Double.TryParse(testStr, NumberStyles.Float, CultureInfo.InvariantCulture, out doubleValue); Assert.AreEqual(Decimal64.FromFixedPoint(1234512345123451, -9), value); @@ -756,7 +756,7 @@ public void TestParseReImpl() { var testStr = "1234512345123451234500000e+12345123451234512345"; uint fpsf; - var value = Decimal64.FromUnderlying(DotNetReImpl.bid64_from_string(testStr, out fpsf, roundMode)); + var value = Decimal64.FromUnderlying(DotNetReImpl.bid64_from_string(testStr, Decimal64.DecimalMarkAny, out fpsf, roundMode)); double doubleValue; var doubleParseOk = Double.TryParse(testStr, NumberStyles.Float, CultureInfo.InvariantCulture, out doubleValue); Assert.AreEqual(Decimal64.PositiveInfinity, value); @@ -766,7 +766,7 @@ public void TestParseReImpl() { var testStr = "-5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; uint fpsf; - var value = Decimal64.FromUnderlying(DotNetReImpl.bid64_from_string(testStr, out fpsf, roundMode)); + var value = Decimal64.FromUnderlying(DotNetReImpl.bid64_from_string(testStr, Decimal64.DecimalMarkAny, out fpsf, roundMode)); double doubleValue; var doubleParseOk = Double.TryParse(testStr, NumberStyles.Float, CultureInfo.InvariantCulture, out doubleValue); Assert.AreEqual(Decimal64.NegativeInfinity, value); @@ -776,7 +776,7 @@ public void TestParseReImpl() { var testStr = "0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005"; uint fpsf; - var value = Decimal64.FromUnderlying(DotNetReImpl.bid64_from_string(testStr, out fpsf, roundMode)); + var value = Decimal64.FromUnderlying(DotNetReImpl.bid64_from_string(testStr, Decimal64.DecimalMarkAny, out fpsf, roundMode)); double doubleValue; var doubleParseOk = Double.TryParse(testStr, NumberStyles.Float, CultureInfo.InvariantCulture, out doubleValue); Assert.AreEqual(Decimal64.Zero, value); @@ -786,7 +786,7 @@ public void TestParseReImpl() { var testStr = "123 x99"; uint fpsf; - var value = Decimal64.FromUnderlying(DotNetReImpl.bid64_from_string(testStr, out fpsf, roundMode)); + var value = Decimal64.FromUnderlying(DotNetReImpl.bid64_from_string(testStr, Decimal64.DecimalMarkAny, out fpsf, roundMode)); double doubleValue; var doubleParseOk = Double.TryParse(testStr, NumberStyles.Float, CultureInfo.InvariantCulture, out doubleValue); Assert.AreEqual(Decimal64.NaN, value); diff --git a/csharp/EPAM.Deltix.DFP/Decimal64.cs b/csharp/EPAM.Deltix.DFP/Decimal64.cs index 81a2a8a3..07f708e0 100644 --- a/csharp/EPAM.Deltix.DFP/Decimal64.cs +++ b/csharp/EPAM.Deltix.DFP/Decimal64.cs @@ -952,7 +952,18 @@ public Decimal64 Canonize() public static Decimal64 Parse(String text) { uint fpsf; - var ret = DotNetReImpl.bid64_from_string(text, out fpsf); + var ret = DotNetReImpl.bid64_from_string(text, DecimalMarkAny, out fpsf); + if ((fpsf & DotNetReImpl.BID_INVALID_FORMAT) != 0) + throw new FormatException("Input string is not in a correct format."); + //else if ((fpsf & DotNetReImpl.BID_INEXACT_EXCEPTION) != 0) + // throw new FormatException("Can't convert input string to value without precision loss."); + return FromUnderlying(ret); + } + + public static Decimal64 Parse(String text, String decimalMarks) + { + uint fpsf; + var ret = DotNetReImpl.bid64_from_string(text, decimalMarks, out fpsf); if ((fpsf & DotNetReImpl.BID_INVALID_FORMAT) != 0) throw new FormatException("Input string is not in a correct format."); //else if ((fpsf & DotNetReImpl.BID_INEXACT_EXCEPTION) != 0) @@ -976,7 +987,15 @@ public enum StatusValue public static Boolean TryParse(String text, out Decimal64 result, out StatusValue status) { uint fpsf; - result = FromUnderlying(DotNetReImpl.bid64_from_string(text, out fpsf)); + result = FromUnderlying(DotNetReImpl.bid64_from_string(text, DecimalMarkAny, out fpsf)); + status = (StatusValue)fpsf; + return status == StatusValue.Exact; + } + + public static Boolean TryParse(String text, String decimalMarks, out Decimal64 result, out StatusValue status) + { + uint fpsf; + result = FromUnderlying(DotNetReImpl.bid64_from_string(text, decimalMarks, out fpsf)); status = (StatusValue)fpsf; return status == StatusValue.Exact; } @@ -984,7 +1003,20 @@ public static Boolean TryParse(String text, out Decimal64 result, out StatusValu public static Boolean TryParse(String text, out Decimal64 result) { uint fpsf; - var ret = DotNetReImpl.bid64_from_string(text, out fpsf); + var ret = DotNetReImpl.bid64_from_string(text, DecimalMarkAny, out fpsf); + if ((fpsf & DotNetReImpl.BID_INVALID_FORMAT) != 0) + { + result = NaN; + return false; + } + result = FromUnderlying(ret); + return true; + } + + public static Boolean TryParse(String text, String decimalMarks, out Decimal64 result) + { + uint fpsf; + var ret = DotNetReImpl.bid64_from_string(text, decimalMarks, out fpsf); if ((fpsf & DotNetReImpl.BID_INVALID_FORMAT) != 0) { result = NaN; diff --git a/csharp/EPAM.Deltix.DFP/DotNetReImpl.cs b/csharp/EPAM.Deltix.DFP/DotNetReImpl.cs index 0ccdd9bf..3187f37f 100644 --- a/csharp/EPAM.Deltix.DFP/DotNetReImpl.cs +++ b/csharp/EPAM.Deltix.DFP/DotNetReImpl.cs @@ -76,7 +76,7 @@ private unsafe static bool IsStrEq(char* ptr, string str) } } - public unsafe static BID_UINT64 bid64_from_string(string s, out _IDEC_flags pfpsf, string decimalMarks, int rnd_mode = BID_ROUNDING_TO_NEAREST/*, _EXC_MASKS_PARAM _EXC_INFO_PARAM*/) + public unsafe static BID_UINT64 bid64_from_string(string s, string decimalMarks, out _IDEC_flags pfpsf, int rnd_mode = BID_ROUNDING_TO_NEAREST/*, _EXC_MASKS_PARAM _EXC_INFO_PARAM*/) { BID_UINT64 coefficient_x = 0, rounded = 0; From ac73404d576c2c87a98352165821ef76a7a38da1 Mon Sep 17 00:00:00 2001 From: Andrei Davydov Date: Wed, 20 Sep 2023 12:14:41 +0300 Subject: [PATCH 5/5] .NET: Parse(): Fix wrong decimalMark check --- csharp/EPAM.Deltix.DFP/DotNetReImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/csharp/EPAM.Deltix.DFP/DotNetReImpl.cs b/csharp/EPAM.Deltix.DFP/DotNetReImpl.cs index 3187f37f..5f46a953 100644 --- a/csharp/EPAM.Deltix.DFP/DotNetReImpl.cs +++ b/csharp/EPAM.Deltix.DFP/DotNetReImpl.cs @@ -110,7 +110,7 @@ public unsafe static BID_UINT64 bid64_from_string(string s, string decimalMarks, bool cEqDot = decimalMarks.IndexOf(*ps) >= 0; // detect special cases (INF or NaN) - if (cEqDot && (*ps < '0' || *ps > '9')) + if (!cEqDot && (*ps < '0' || *ps > '9')) { if (IsStrEq(ps, "inf") || IsStrEq(ps, "infinity")) // Infinity? {