Skip to content

Commit 9d70cad

Browse files
committed
#777 Add COMP binary encoders implementation.
1 parent 504f7cf commit 9d70cad

File tree

4 files changed

+221
-39
lines changed

4 files changed

+221
-39
lines changed
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright 2018 ABSA Group Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package za.co.absa.cobrix.cobol.parser.encoding
18+
19+
import java.math.RoundingMode
20+
21+
object BinaryEncoders {
22+
def encodeBinaryNumber(number: java.math.BigDecimal,
23+
isSigned: Boolean,
24+
outputSize: Int,
25+
bigEndian: Boolean,
26+
precision: Int,
27+
scale: Int,
28+
scaleFactor: Int): Array[Byte] = {
29+
val bytes = new Array[Byte](outputSize)
30+
31+
if (number == null || precision < 1 || scale < 0 || outputSize < 1)
32+
return bytes
33+
34+
val shift = scaleFactor - scale
35+
val bigInt = if (shift == 0)
36+
number.setScale(0, RoundingMode.HALF_DOWN).toBigIntegerExact
37+
else
38+
number.movePointLeft(shift).setScale(0, RoundingMode.HALF_DOWN).toBigIntegerExact
39+
40+
val intValue = bigInt.toByteArray
41+
val intValueLen = intValue.length
42+
43+
if (intValueLen > outputSize || (!isSigned && bigInt.signum() < 0))
44+
return bytes
45+
46+
val paddingByte = if (bigInt.signum() < 0) 0xFF.toByte else 0x00.toByte
47+
48+
if (bigEndian) {
49+
var i = 0
50+
while (i < outputSize) {
51+
if (i < intValueLen) {
52+
bytes(outputSize - i - 1) = intValue(intValueLen - i - 1)
53+
} else {
54+
bytes(outputSize - i - 1) = paddingByte
55+
}
56+
i += 1
57+
}
58+
} else {
59+
var i = 0
60+
while (i < outputSize) {
61+
if (i < intValueLen) {
62+
bytes(i) = intValue(intValueLen - i - 1)
63+
} else {
64+
bytes(i) = paddingByte
65+
}
66+
i += 1
67+
}
68+
}
69+
bytes
70+
}
71+
}

cobol-parser/src/test/scala/za/co/absa/cobrix/cobol/parser/encoding/BCDNumberEncodersSuite.scala

Lines changed: 27 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616

1717
package za.co.absa.cobrix.cobol.parser.encoding
1818

19-
import org.scalatest.Assertion
2019
import org.scalatest.wordspec.AnyWordSpec
20+
import za.co.absa.cobrix.cobol.testutils.ComparisonUtils._
2121

2222
class BCDNumberEncodersSuite extends AnyWordSpec {
2323
"encodeBCDNumber" should {
@@ -26,105 +26,105 @@ class BCDNumberEncodersSuite extends AnyWordSpec {
2626
val expected = Array[Byte](0x12, 0x34, 0x5C)
2727
val actual = BCDNumberEncoders.encodeBCDNumber(new java.math.BigDecimal(12345), 5, 0, 0, signed = true, mandatorySignNibble = true)
2828

29-
checkExpected(actual, expected)
29+
assertArraysEqual(actual, expected)
3030
}
3131

3232
"encode a number with an even precision" in {
3333
val expected = Array[Byte](0x01, 0x23, 0x4C)
3434
val actual = BCDNumberEncoders.encodeBCDNumber(new java.math.BigDecimal(1234), 4, 0, 0, signed = true, mandatorySignNibble = true)
3535

36-
checkExpected(actual, expected)
36+
assertArraysEqual(actual, expected)
3737
}
3838

3939
"encode a small number" in {
4040
val expected = Array[Byte](0x00, 0x00, 0x5C)
4141
val actual = BCDNumberEncoders.encodeBCDNumber(new java.math.BigDecimal(5), 5, 0, 0, signed = true, mandatorySignNibble = true)
4242

43-
checkExpected(actual, expected)
43+
assertArraysEqual(actual, expected)
4444
}
4545

4646
"encode an unsigned number" in {
4747
val expected = Array[Byte](0x12, 0x34, 0x5F)
4848
val actual = BCDNumberEncoders.encodeBCDNumber(new java.math.BigDecimal(12345), 5, 0, 0, signed = false, mandatorySignNibble = true)
4949

50-
checkExpected(actual, expected)
50+
assertArraysEqual(actual, expected)
5151
}
5252

5353
"encode a negative number" in {
5454
val expected = Array[Byte](0x12, 0x34, 0x5D)
5555
val actual = BCDNumberEncoders.encodeBCDNumber(new java.math.BigDecimal(-12345), 5, 0, 0, signed = true, mandatorySignNibble = true)
5656

57-
checkExpected(actual, expected)
57+
assertArraysEqual(actual, expected)
5858
}
5959

6060
"encode a small negative number" in {
6161
val expected = Array[Byte](0x00, 0x00, 0x7D)
6262
val actual = BCDNumberEncoders.encodeBCDNumber(new java.math.BigDecimal(-7), 4, 0, 0, signed = true, mandatorySignNibble = true)
6363

64-
checkExpected(actual, expected)
64+
assertArraysEqual(actual, expected)
6565
}
6666

6767
"encode a number without sign nibble" in {
6868
val expected = Array[Byte](0x01, 0x23, 0x45)
6969
val actual = BCDNumberEncoders.encodeBCDNumber(new java.math.BigDecimal(12345), 5, 0, 0, signed = false, mandatorySignNibble = false)
7070

71-
checkExpected(actual, expected)
71+
assertArraysEqual(actual, expected)
7272
}
7373

7474
"encode a number without sign nibble with an even precision" in {
7575
val expected = Array[Byte](0x12, 0x34)
7676
val actual = BCDNumberEncoders.encodeBCDNumber(new java.math.BigDecimal(1234), 4, 0, 0, signed = true, mandatorySignNibble = false)
7777

78-
checkExpected(actual, expected)
78+
assertArraysEqual(actual, expected)
7979
}
8080

8181
"encode a too big number" in {
8282
val expected = Array[Byte](0x00, 0x00, 0x00)
8383
val actual = BCDNumberEncoders.encodeBCDNumber(new java.math.BigDecimal(123456), 5, 0, 0, signed = false, mandatorySignNibble = false)
8484

85-
checkExpected(actual, expected)
85+
assertArraysEqual(actual, expected)
8686
}
8787

8888
"encode a too big negative number" in {
8989
val expected = Array[Byte](0x00, 0x00, 0x00)
9090
val actual = BCDNumberEncoders.encodeBCDNumber(new java.math.BigDecimal(-123456), 5, 0, 0, signed = true, mandatorySignNibble = true)
9191

92-
checkExpected(actual, expected)
92+
assertArraysEqual(actual, expected)
9393
}
9494

9595
"encode a number with nbegative scale" in {
9696
val expected = Array[Byte](0x00, 0x00, 0x00)
9797
val actual = BCDNumberEncoders.encodeBCDNumber(new java.math.BigDecimal(12345), 5, -1, 0, signed = false, mandatorySignNibble = false)
9898

99-
checkExpected(actual, expected)
99+
assertArraysEqual(actual, expected)
100100
}
101101

102102
"attempt to encode a negative number without sign nibble" in {
103103
val expected = Array[Byte](0x00, 0x00, 0x00)
104104
val actual = BCDNumberEncoders.encodeBCDNumber(new java.math.BigDecimal(-12345), 5, 0, 0, signed = false, mandatorySignNibble = false)
105105

106-
checkExpected(actual, expected)
106+
assertArraysEqual(actual, expected)
107107
}
108108

109109
"attempt to encode a signed number without a sign nibble" in {
110110
val expected = Array[Byte](0x00, 0x00, 0x00)
111111
val actual = BCDNumberEncoders.encodeBCDNumber(new java.math.BigDecimal(-12345), 5, 0, 0, signed = true, mandatorySignNibble = false)
112112

113-
checkExpected(actual, expected)
113+
assertArraysEqual(actual, expected)
114114
}
115115

116116
"attempt to encode a number with an incorrect precision" in {
117117
val expected = Array[Byte](0x00, 0x00)
118118
val actual = BCDNumberEncoders.encodeBCDNumber(new java.math.BigDecimal(12345), 4, 0, 0, signed = false, mandatorySignNibble = false)
119119

120-
checkExpected(actual, expected)
120+
assertArraysEqual(actual, expected)
121121
}
122122

123123
"attempt to encode a number with an incorrect precision with sign nibble" in {
124124
val expected = Array[Byte](0x00, 0x00, 0x00)
125125
val actual = BCDNumberEncoders.encodeBCDNumber(new java.math.BigDecimal(12345), 4, 0, 0, signed = true, mandatorySignNibble = true)
126126

127-
checkExpected(actual, expected)
127+
assertArraysEqual(actual, expected)
128128
}
129129

130130
"attempt to encode a number with zero prexision" in {
@@ -137,90 +137,78 @@ class BCDNumberEncodersSuite extends AnyWordSpec {
137137
val expected = Array[Byte](0x12, 0x34, 0x5C)
138138
val actual = BCDNumberEncoders.encodeBCDNumber(new java.math.BigDecimal(123.45), 5, 2, 0, signed = true, mandatorySignNibble = true)
139139

140-
checkExpected(actual, expected)
140+
assertArraysEqual(actual, expected)
141141
}
142142

143143
"encode a small number" in {
144144
val expected = Array[Byte](0x00, 0x00, 0x5C)
145145
val actual = BCDNumberEncoders.encodeBCDNumber(new java.math.BigDecimal(0.05), 5, 2, 0, signed = true, mandatorySignNibble = true)
146146

147-
checkExpected(actual, expected)
147+
assertArraysEqual(actual, expected)
148148
}
149149

150150
"encode an unsigned number" in {
151151
val expected = Array[Byte](0x12, 0x34, 0x5F)
152152
val actual = BCDNumberEncoders.encodeBCDNumber(new java.math.BigDecimal(1234.5), 5, 1, 0, signed = false, mandatorySignNibble = true)
153153

154-
checkExpected(actual, expected)
154+
assertArraysEqual(actual, expected)
155155
}
156156

157157
"encode a negative number" in {
158158
val expected = Array[Byte](0x12, 0x34, 0x5D)
159159
val actual = BCDNumberEncoders.encodeBCDNumber(new java.math.BigDecimal(-12.345), 5, 3, 0, signed = true, mandatorySignNibble = true)
160160

161-
checkExpected(actual, expected)
161+
assertArraysEqual(actual, expected)
162162
}
163163

164164
"encode a small negative number" in {
165165
val expected = Array[Byte](0x00, 0x00, 0x7D)
166166
val actual = BCDNumberEncoders.encodeBCDNumber(new java.math.BigDecimal(-0.00007), 4, 5, 0, signed = true, mandatorySignNibble = true)
167167

168-
checkExpected(actual, expected)
168+
assertArraysEqual(actual, expected)
169169
}
170170

171171
"encode a number without sign nibble" in {
172172
val expected = Array[Byte](0x01, 0x23, 0x45)
173173
val actual = BCDNumberEncoders.encodeBCDNumber(new java.math.BigDecimal(123.45), 5, 2, 0, signed = false, mandatorySignNibble = false)
174174

175-
checkExpected(actual, expected)
175+
assertArraysEqual(actual, expected)
176176
}
177177

178178
"encode a too precise number" in {
179179
val expected = Array[Byte](0x01, 0x23, 0x46)
180180
val actual = BCDNumberEncoders.encodeBCDNumber(new java.math.BigDecimal(123.456), 5, 2, 0, signed = false, mandatorySignNibble = false)
181181

182-
checkExpected(actual, expected)
182+
assertArraysEqual(actual, expected)
183183
}
184184

185185
"encode a too big number" in {
186186
val expected = Array[Byte](0x00, 0x00, 0x00)
187187
val actual = BCDNumberEncoders.encodeBCDNumber(new java.math.BigDecimal(1234.56), 5, 2, 0, signed = false, mandatorySignNibble = false)
188188

189-
checkExpected(actual, expected)
189+
assertArraysEqual(actual, expected)
190190
}
191191

192192
"encode a too big negative number" in {
193193
val expected = Array[Byte](0x00, 0x00, 0x00)
194194
val actual = BCDNumberEncoders.encodeBCDNumber(new java.math.BigDecimal(-1234.56), 5, 2, 0, signed = true, mandatorySignNibble = true)
195195

196-
checkExpected(actual, expected)
196+
assertArraysEqual(actual, expected)
197197
}
198198

199199
"encode a number with positive scale factor" in {
200200
val expected = Array[Byte](0x00, 0x12, 0x3F)
201201
val actual = BCDNumberEncoders.encodeBCDNumber(new java.math.BigDecimal(12300), 5, 0, 2, signed = false, mandatorySignNibble = true)
202202

203-
checkExpected(actual, expected)
203+
assertArraysEqual(actual, expected)
204204
}
205205

206206
"encode a number with negative scale factor" in {
207207
val expected = Array[Byte](0x00, 0x12, 0x3F)
208208
val actual = BCDNumberEncoders.encodeBCDNumber(new java.math.BigDecimal(1.23), 5, 0, -2, signed = false, mandatorySignNibble = true)
209209

210-
checkExpected(actual, expected)
210+
assertArraysEqual(actual, expected)
211211
}
212212
}
213213
}
214-
215-
def checkExpected(actual: Array[Byte], expected: Array[Byte]): Assertion = {
216-
if (!actual.sameElements(expected)) {
217-
val actualHex = actual.map(b => f"$b%02X").mkString(" ")
218-
val expectedHex = expected.map(b => f"$b%02X").mkString(" ")
219-
fail(s"Actual: $actualHex\nExpected: $expectedHex")
220-
} else {
221-
succeed
222-
}
223-
}
224-
225-
226214
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Copyright 2018 ABSA Group Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package za.co.absa.cobrix.cobol.parser.encoding
18+
19+
import org.scalatest.wordspec.AnyWordSpec
20+
import za.co.absa.cobrix.cobol.testutils.ComparisonUtils._
21+
22+
class BinaryEncodersSuite extends AnyWordSpec {
23+
"encodeBinaryNumber" should {
24+
"encode a positive integer in big-endian format" in {
25+
val expected = Array(0x00, 0x00, 0x30, 0x39).map(_.toByte) // 12345 in hex
26+
val actual = BinaryEncoders.encodeBinaryNumber(new java.math.BigDecimal(12345), isSigned = true, outputSize = 4, bigEndian = true, precision = 5, scale = 0, scaleFactor = 0)
27+
28+
assertArraysEqual(actual, expected)
29+
}
30+
31+
"encode a positive integer in little-endian format" in {
32+
val expected = Array(0x39, 0x30, 0x00, 0x00).map(_.toByte) // 12345 in hex reversed
33+
val actual = BinaryEncoders.encodeBinaryNumber(new java.math.BigDecimal(12345), isSigned = true, outputSize = 4, bigEndian = false, precision = 5, scale = 0, scaleFactor = 0)
34+
35+
assertArraysEqual(actual, expected)
36+
}
37+
38+
"encode a negative integer -1 big-endian format" in {
39+
val expected = Array(0xFF, 0xFF, 0xFF, 0xFF).map(_.toByte)
40+
val actual = BinaryEncoders.encodeBinaryNumber(new java.math.BigDecimal(-1), isSigned = true, outputSize = 4, bigEndian = true, precision = 5, scale = 0, scaleFactor = 0)
41+
42+
assertArraysEqual(actual, expected)
43+
}
44+
45+
"encode a negative integer in big-endian format" in {
46+
val expected = Array(0xFF, 0xFF, 0xCF, 0xC7).map(_.toByte) // -12345 in hex (two's complement)
47+
val actual = BinaryEncoders.encodeBinaryNumber(new java.math.BigDecimal(-12345), isSigned = true, outputSize = 4, bigEndian = true, precision = 5, scale = 0, scaleFactor = 0)
48+
49+
assertArraysEqual(actual, expected)
50+
}
51+
52+
"encode a negative integer -1 little-endian format" in {
53+
val expected = Array(0xFF, 0xFF, 0xFF, 0xFF).map(_.toByte)
54+
val actual = BinaryEncoders.encodeBinaryNumber(new java.math.BigDecimal(-1), isSigned = true, outputSize = 4, bigEndian = false, precision = 4, scale = 0, scaleFactor = 0)
55+
// bvnvjh well well well ijg9 g5-0itg -30it 0ho 06 =-uo 65=-uo =u-ou
56+
assertArraysEqual(actual, expected)
57+
}
58+
59+
"encode a negative integer in little-endian format" in {
60+
val expected = Array(0xC7, 0xCF, 0xFF, 0xFF).map(_.toByte) // -12345 in hex reversed (two's complement)
61+
val actual = BinaryEncoders.encodeBinaryNumber(new java.math.BigDecimal(-12345), isSigned = true, outputSize = 4, bigEndian = false, precision = 5, scale = 0, scaleFactor = 0)
62+
63+
assertArraysEqual(actual, expected)
64+
}
65+
66+
"attempt to encode a number with the maximum digits of the precision" in {
67+
val expected = Array(0xF1, 0xD8, 0xFF, 0xFF).map(_.toByte)
68+
val actual = BinaryEncoders.encodeBinaryNumber(new java.math.BigDecimal(-9999), isSigned = true, outputSize = 4, bigEndian = false, precision = 4, scale = 0, scaleFactor = 0)
69+
70+
assertArraysEqual(actual, expected)
71+
}
72+
73+
"attempt to encode a number bigger than the precision" in {
74+
val expected = Array(0xF0, 0xD8, 0xFF, 0xFF).map(_.toByte)
75+
val actual = BinaryEncoders.encodeBinaryNumber(new java.math.BigDecimal(-10000), isSigned = true, outputSize = 4, bigEndian = false, precision = 4, scale = 0, scaleFactor = 0)
76+
77+
assertArraysEqual(actual, expected)
78+
}
79+
80+
"handle zero correctly" in {
81+
val expected = Array[Byte](0x00, 0x00, 0x00, 0x00)
82+
83+
val actualBigEndian = BinaryEncoders.encodeBinaryNumber(new java.math.BigDecimal(0), isSigned = true, outputSize = 4, bigEndian = true, precision = 1, scale = 0, scaleFactor = 0)
84+
val actualLittleEndian = BinaryEncoders.encodeBinaryNumber(new java.math.BigDecimal(0), isSigned = true, outputSize = 4, bigEndian = false, precision = 1, scale = 0, scaleFactor = 0)
85+
86+
assertArraysEqual(actualBigEndian, expected)
87+
assertArraysEqual(actualLittleEndian, expected)
88+
}
89+
}
90+
}

0 commit comments

Comments
 (0)