Skip to content

Commit

Permalink
Merge pull request #699 from smeup/bugfix/LS25000341/encode-decode-fo…
Browse files Browse the repository at this point in the history
…r-packet-of-ds

Bugfix/LS25000341/Encode-Decode for Packed of DS
  • Loading branch information
lanarimarco authored Feb 6, 2025
2 parents 3fdc667 + 692ee9b commit bc2d503
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.smeup.rpgparser.interpreter

import com.smeup.rpgparser.parsing.parsetreetoast.RpgType
import com.smeup.rpgparser.parsing.parsetreetoast.isNumber
import com.smeup.rpgparser.utils.repeatWithMaxSize
import java.math.BigDecimal
import java.math.RoundingMode
Expand Down Expand Up @@ -135,8 +136,14 @@ private fun coerceString(value: StringValue, type: Type): Value {
}
else -> {
if (!value.isBlank()) {
val intValue = decodeFromDS(value.value.trim(), type.entireDigits, type.decimalDigits)
IntValue(intValue.longValueExact())
val intValue = value.value.trim()
if (intValue.isNumber()) {
IntValue(intValue.toLong())
} else {
// A Packed could end with a char. Consider MUDRNRAPU00115.
val packedValue = decodeFromPacked(value.value.trimEnd(), type.entireDigits, type.decimalDigits)
IntValue(packedValue.longValueExact())
}
} else {
IntValue(0)
}
Expand All @@ -150,8 +157,21 @@ private fun coerceString(value: StringValue, type: Type): Value {
DecimalValue(decimalValue)
}
else -> {
val decimalValue = decodeFromDS(value.value.trim(), type.entireDigits, type.decimalDigits)
DecimalValue(decimalValue)
/*
* FIXME: Could be wrong. Have to reach only an encoded number and not a clean. For example,
* during the execution of `MUDRNRAPU00254`, at this point arrives:
* NumberType(entireDigits=21, decimalDigits=9, rpgType=P)
* and:
* StringValue[11](1.000000000)
*/
val decimalValue = value.value.trim()
if (decimalValue.isNumber()) {
DecimalValue(decimalValue.toBigDecimal())
} else {
// A Packed could end with a char. Consider MUDRNRAPU00115.
val packedValue = decodeFromPacked(value.value.trimEnd(), type.entireDigits, type.decimalDigits) // A Packed could end always with a char.
DecimalValue(packedValue)
}
}
}
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,13 +262,13 @@ fun Type.toDataStructureValue(value: Value): StringValue {
if (this.rpgType == RpgType.PACKED.rpgType || this.rpgType == "") {
return if (this.decimal) {
// Transform the numeric to an encoded string
val encoded = encodeToDS(value.asDecimal().value, this.entireDigits, this.decimalDigits)
val encoded = encodeToPacked(value.asDecimal().value, this.entireDigits, this.decimalDigits)
// adjust the size to fit the target field
val fitted = encoded.padEnd(this.size)
StringValue(fitted)
} else {
// Transform the numeric to an encoded string
val encoded = encodeToDS(value.asDecimal().value, this.entireDigits, 0)
val encoded = encodeToPacked(value.asDecimal().value, this.entireDigits, 0)
// adjust the size to fit the target field
val fitted = encoded.padEnd(this.size)
StringValue(fitted)
Expand Down Expand Up @@ -730,7 +730,7 @@ fun decodeFromZoned(value: String, digits: Int, scale: Int): BigDecimal {
/**
* Encoding/Decoding a numeric value for a data structure
*/
fun encodeToDS(inValue: BigDecimal, digits: Int, scale: Int): String {
fun encodeToPacked(inValue: BigDecimal, digits: Int, scale: Int): String {
// get just the digits from BigDecimal, "normalize" away sign, decimal place etc.
val inChars = inValue.abs().movePointRight(scale).toBigInteger().toString().toCharArray()
val buffer = IntArray(inChars.size / 2 + 1)
Expand Down Expand Up @@ -770,45 +770,41 @@ fun encodeToDS(inValue: BigDecimal, digits: Int, scale: Int): String {
return s
}

fun decodeFromDS(value: String, digits: Int, scale: Int): BigDecimal {
return try {
value.toBigDecimal()
} catch (e: Exception) {
val buffer = IntArray(value.length)
for (i in value.indices) {
buffer[i] = value[i].code
}
fun decodeFromPacked(value: String, digits: Int, scale: Int): BigDecimal {
val buffer = IntArray(value.length)
for (i in value.indices) {
buffer[i] = value[i].code
}

val sign = StringBuilder()
val number = StringBuilder()
var nibble = (buffer[buffer.size - 1] and 0x0F)
if (nibble == 0x0B || nibble == 0x0D) {
sign.append("-")
}
val sign = StringBuilder()
val number = StringBuilder()
var nibble = (buffer[buffer.size - 1] and 0x0F)
if (nibble == 0x0B || nibble == 0x0D) {
sign.append("-")
}

var offset = 0
while (offset < (buffer.size - 1)) {
nibble = (buffer[offset] and 0xFF).ushr(4)
number.append((nibble or 0x30).toChar())
nibble = buffer[offset] and 0x0F or 0x30
number.append((nibble or 0x30).toChar())
var offset = 0
while (offset < (buffer.size - 1)) {
nibble = (buffer[offset] and 0xFF).ushr(4)
number.append((nibble or 0x30).toChar())
nibble = buffer[offset] and 0x0F or 0x30
number.append((nibble or 0x30).toChar())

offset++
}
offset++
}

// read last digit
nibble = (buffer[offset] and 0xFF).ushr(4)
if (nibble <= 9) {
number.append((nibble or 0x30).toChar())
}
// adjust the scale
if (scale > 0 && number.toString() != "0") {
val len = number.length
number.insert(len - scale, ".")
}
number.insert(0, sign)
number.toString().toBigDecimal()
// read last digit
nibble = (buffer[offset] and 0xFF).ushr(4)
if (nibble <= 9) {
number.append((nibble or 0x30).toChar())
}
// adjust the scale
if (scale > 0 && number.toString() != "0") {
val len = number.length
number.insert(len - scale, ".")
}
number.insert(0, sign)
return number.toString().toBigDecimal()
}

enum class Visibility {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,9 @@ class RpgDataEncoderTest {

private val encodeDecodePackedLambda: Int.() -> Boolean = {
val packed50 = toBigDecimal(MathContext(0))
val encoded50 = encodeToDS(packed50, 5, 0)
val encoded50 = encodeToPacked(packed50, 5, 0)
assertTrue(encoded50.length <= 7)
val decoded50 = decodeFromDS(encoded50, 5, 0)
val decoded50 = decodeFromPacked(encoded50, 5, 0)
assertEquals(packed50.compareTo(decoded50), 0)
true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ import com.smeup.rpgparser.parsing.parsetreetoast.RpgType
import com.smeup.rpgparser.parsing.parsetreetoast.ToAstConfiguration
import com.smeup.rpgparser.parsing.parsetreetoast.resolveAndValidate
import org.junit.experimental.categories.Category
import kotlin.random.Random
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
import kotlin.time.measureTime
import kotlin.time.*
import org.junit.Test as test

open class DataDefinitionTest : AbstractTest() {
Expand Down Expand Up @@ -364,10 +365,44 @@ class DataDefinitionPerformanceTest : AbstractTest() {
fun decodeFromDS() {
measureTime {
for (i in 1..100_000_000) {
decodeFromDS(value = "123456789.15", digits = 20, scale = 2)
decodeFromPacked(value = "123456789.15", digits = 20, scale = 2)
}
}.also {
println("decodeFromDS: $it")
}
}
}

/**
* Performance test for encoding and decoding packed decimal values.
*
* This test measures the performance of the `encodeToPacked` and `decodeFromPacked` functions by repeatedly
* encoding and decoding a set of random decimal numbers. The test generates 1000 random decimal numbers
* within a specified range and then performs 1000 encode/decode cycles.
*
* The test focuses on the end-to-end encoding/decoding cycle and measures the *total* time taken for all
* operations.
*
* The test uses `measureTime` to capture the execution time of the entire set of encoding/decoding operations.
* The total time is printed to the console.
*
* @see encodeToPacked
* @see decodeFromPacked
*/
@Test(timeout = 3_000)
@Category(PerformanceTest::class)
fun encodeDecodePackedPerformanceTest1() {
val nRandomNumbers = 1000

val valueFrom = "-".plus("9".repeat(21).plus(".").plus("9".repeat(9))).toBigDecimal()
val valueTo = "9".repeat(21).plus(".").plus("9".repeat(9)).toBigDecimal()
val randomNumbers = List(nRandomNumbers) { Random.nextDouble(valueFrom.toDouble(), valueTo.toDouble()) }

val timeMeasurements = measureTime {
randomNumbers.forEach { randomNumber ->
decodeFromPacked(encodeToPacked(randomNumber.toBigDecimal(), 30, 9), 30, 9)
}
}

println("Time execution of encoding/decoding for $nRandomNumbers random numbers is: $timeMeasurements.")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -886,6 +886,27 @@ open class MULANGT02ConstAndDSpecTest : MULANGTTest() {
assertEquals(expected, "smeup/MUDRNRAPU00190".outputOf(configuration = smeupConfig))
}

/**
* This program declares and uses variables and fields as Packed for a simple math operation.
* @see #LS25000341
*/
@Test
fun executeMUDRNRAPU00193() {
val expected = listOf("STD: 40461860", "DS: 40461860", "STD: 99999999", "DS: 99999999")
assertEquals(expected, "smeup/MUDRNRAPU00193".outputOf(configuration = smeupConfig))
}

/**
* This program declares and uses variables and fields as Standalone for a simple math operation,
* like `MUDRNRAPU00193` test.
* @see #LS25000341
*/
@Test
fun executeMUDRNRAPU00194() {
val expected = listOf("STD: 40461860", "DS: 40461860", "STD: 99999999", "DS: 99999999")
assertEquals(expected, "smeup/MUDRNRAPU00194".outputOf(configuration = smeupConfig))
}

/**
* Definitions with LIKE referencing a DS must be defined as strings with the same size as the DS
* @see #LS25000333
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
V* ==============================================================
V* 22/01/2025 APU001 Creation
V* ==============================================================
O * PROGRAM GOAL
O * This program declares and uses variables and fields as Packed
O * for a simple math operation.
V* ==============================================================
O * JARIKO ANOMALY
O * On Jariko the final result about the sum between two DS fields
O * are wrong and different.
O * This is caused from wrong de-codification of Packed.
V* ==============================================================
D STD1 S 8P 0
D STD2 S 8P 0
D DS1 DS
D DS1_F1 8P 0
D DS1_F2 8P 0

D RES S 8P 0
D MSG S 25

C EVAL STD1=20230930
C EVAL STD2=20230930
C EVAL RES=STD1+STD2
C EVAL MSG='STD: ' +
C %CHAR(RES)
C MSG DSPLY

C EVAL DS1_F1=20230930
C EVAL DS1_F2=20230930
C EVAL RES=DS1_F1+DS1_F2
C EVAL MSG='DS: ' +
C %CHAR(RES)
C MSG DSPLY

C EVAL STD1=12345678
C EVAL STD2=87654321
C EVAL RES=STD1+STD2
C EVAL MSG='STD: ' +
C %CHAR(RES)
C MSG DSPLY

C EVAL DS1_F1=12345678
C EVAL DS1_F2=87654321
C EVAL RES=DS1_F1+DS1_F2
C EVAL MSG='DS: ' +
C %CHAR(RES)
C MSG DSPLY

C SETON LR
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
V* ==============================================================
V* 22/01/2025 APU001 Creation
V* ==============================================================
O * PROGRAM GOAL
O * This program declares and uses variables and fields as
O * Standalone for a simple math operation,
O * like `MUDRNRAPU00193` test.
V* ==============================================================
D STD1 S 8 0
D STD2 S 8 0
D DS1 DS
D DS1_F1 8 0
D DS1_F2 8 0

D RES S 8 0
D MSG S 25

C EVAL STD1=20230930
C EVAL STD2=20230930
C EVAL RES=STD1+STD2
C EVAL MSG='STD: ' +
C %CHAR(RES)
C MSG DSPLY

C EVAL DS1_F1=20230930
C EVAL DS1_F2=20230930
C EVAL RES=DS1_F1+DS1_F2
C EVAL MSG='DS: ' +
C %CHAR(RES)
C MSG DSPLY

C EVAL STD1=12345678
C EVAL STD2=87654321
C EVAL RES=STD1+STD2
C EVAL MSG='STD: ' +
C %CHAR(RES)
C MSG DSPLY

C EVAL DS1_F1=12345678
C EVAL DS1_F2=87654321
C EVAL RES=DS1_F1+DS1_F2
C EVAL MSG='DS: ' +
C %CHAR(RES)
C MSG DSPLY

C SETON LR

0 comments on commit bc2d503

Please sign in to comment.