Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds support for writing Ion 1.1 decimal binary encoding #636

Merged
merged 1 commit into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions src/com/amazon/ion/impl/bin/IonEncoder_1_1.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.amazon.ion.impl.bin;

import com.amazon.ion.Decimal;
import com.amazon.ion.IonType;
import com.amazon.ion.Timestamp;

Expand Down Expand Up @@ -158,6 +159,50 @@ public static int writeFloat(WriteBuffer buffer, final double value) {
}
}

public static int writeDecimalValue(WriteBuffer buffer, final BigDecimal value) {
if (value == null) {
return writeNullValue(buffer, IonType.DECIMAL);
}

int exponent = -value.scale();

if (BigDecimal.ZERO.compareTo(value) == 0 && !Decimal.isNegativeZero(value)) {
if (exponent == 0) {
buffer.writeByte(OpCodes.DECIMAL_ZERO_LENGTH);
return 1;
} else {
// A decimal with a coefficient of +0 is encoded using opcode 6F.
// The opcode is followed by a FlexInt representing the exponent.
buffer.writeByte(OpCodes.POSITIVE_ZERO_DECIMAL);
return 1 + buffer.writeFlexInt(exponent);
}
}

BigInteger coefficient = value.unscaledValue();
int numCoefficientBytes = WriteBuffer.flexIntLength(coefficient);

int numExponentBytes = 0;
if (exponent != 0) {
numExponentBytes = WriteBuffer.fixedIntLength(exponent);
}

int opCodeAndLengthBytes = 1;
if (numExponentBytes + numCoefficientBytes < 15) {
int opCode = OpCodes.DECIMAL_ZERO_LENGTH + numExponentBytes + numCoefficientBytes;
buffer.writeByte((byte) opCode);
} else {
// Decimal values that require more than 14 bytes can be encoded using the variable-length decimal opcode: 0xF6.
buffer.writeByte(OpCodes.VARIABLE_LENGTH_DECIMAL);
opCodeAndLengthBytes += buffer.writeFlexUInt(numExponentBytes + numCoefficientBytes);
}
buffer.writeFlexInt(coefficient);
if (exponent != 0) {
buffer.writeFixedInt(exponent);
}

return opCodeAndLengthBytes + numCoefficientBytes + numExponentBytes;
}

/**
* Writes a Timestamp to the given WriteBuffer using the Ion 1.1 encoding for Ion Timestamps.
* @return the number of bytes written
Expand Down
3 changes: 2 additions & 1 deletion src/com/amazon/ion/impl/bin/OpCodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ private OpCodes() {}

public static final byte DECIMAL_ZERO_LENGTH = 0x60;
// 0x61-0x6E are additional lengths of decimals.
public static final byte NEGATIVE_ZERO_DECIMAL = 0x6F;
public static final byte POSITIVE_ZERO_DECIMAL = 0x6F;

public static final byte TIMESTAMP_YEAR_PRECISION = 0x70;
public static final byte TIMESTAMP_MONTH_PRECISION = 0x71;
Expand All @@ -39,5 +39,6 @@ private OpCodes() {}
public static final byte NULL_TYPED = (byte) 0xEB;

public static final byte VARIABLE_LENGTH_INTEGER = (byte) 0xF5;
public static final byte VARIABLE_LENGTH_DECIMAL = (byte) 0xF6;
public static final byte VARIABLE_LENGTH_TIMESTAMP = (byte) 0xF7;
}
71 changes: 71 additions & 0 deletions test/com/amazon/ion/impl/bin/IonEncoder_1_1Test.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.amazon.ion.impl.bin;

import com.amazon.ion.Decimal;
import com.amazon.ion.IonType;
import com.amazon.ion.Timestamp;
import org.junit.jupiter.api.Assertions;
Expand Down Expand Up @@ -219,6 +220,61 @@ public void testWriteFloatValueForDouble(double value, String expectedBytes) {
assertWritingValue(expectedBytes, value, IonEncoder_1_1::writeFloat);
}

@ParameterizedTest
@CsvSource({
" 0., 60",
" 0e1, 6F 03",
" 0e63, 6F 7F",
" 0e99, 6F 8E 01",
" 0.0, 6F FF",
" 0.00, 6F FD",
" 0.000, 6F FB",
" 0e-64, 6F 81",
" 0e-99, 6F 76 FE",
" -0., 61 01",
" -0e1, 62 01 01",
" -0e3, 62 01 03",
" -0e127, 62 01 7F",
" -0e199, 63 01 C7 00",
" -0e-1, 62 01 FF",
" -0e-2, 62 01 FE",
" -0e-3, 62 01 FD",
" -0e-127, 62 01 81",
" -0e-199, 63 01 39 FF",
" 0.01, 62 03 FE",
" 0.1, 62 03 FF",
" 1, 61 03",
" 1e1, 62 03 01",
" 1e2, 62 03 02",
" 1e127, 62 03 7F",
" 1e128, 63 03 80 00",
" 1e65536, 64 03 00 00 01",
" 2, 61 05",
" 7, 61 0F",
" 14, 61 1D",
" 1.0, 62 15 FF",
" 1.00, 63 92 01 FE",
" 1.27, 63 FE 01 FE",
" 3.142, 63 1A 31 FD",
" 3.14159, 64 7C 59 26 FB",
" 3.141593, 65 98 FD FE 02 FA",
" 3.141592653, 66 B0 C9 1C 68 17 F7",
" 3.14159265359, 67 E0 93 7D 56 49 12 F5",
" 3.1415926535897932, 69 80 4C 43 76 65 9E 9C 6F F0",
" 3.1415926535897932384626434, 6E 00 50 E0 DC F7 CC D6 08 48 99 92 3F 03 E7",
"3.141592653589793238462643383, F6 1F 00 E0 2D 8F A4 21 D0 E7 46 C0 87 AA 89 02 E5",
})
public void testWriteDecimalValue(@ConvertWith(StringToDecimal.class) Decimal value, String expectedBytes) {
assertWritingValue(expectedBytes, value, IonEncoder_1_1::writeDecimalValue);
}

@Test
public void testWriteDecimalValueForNull() {
int numBytes = IonEncoder_1_1.writeDecimalValue(buf, null);
Assertions.assertEquals("EB 03", byteArrayToHex(bytes()));
Assertions.assertEquals(2, numBytes);
}

// Because timestamp subfields are smeared across bytes, it's easier to reason about them in 1s and 0s
// instead of hex digits
@ParameterizedTest
Expand Down Expand Up @@ -457,4 +513,19 @@ protected Timestamp convert(String source) throws ArgumentConversionException {
return Timestamp.valueOf(source);
}
}

/**
* Converts a String to a Decimal for a @Parameterized test
*/
static class StringToDecimal extends TypedArgumentConverter<String, Decimal> {
protected StringToDecimal() {
super(String.class, Decimal.class);
}

@Override
protected Decimal convert(String source) throws ArgumentConversionException {
if (source == null) return null;
return Decimal.valueOf(source);
}
}
}
Loading