@abaplint/runtime 2.13.36 → 2.13.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -44,14 +44,13 @@ class WriteStatement {
44
44
  }
45
45
  }
46
46
  else if (source instanceof types_1.Packed) {
47
- let num = source.get();
48
- let decimals = source.getDecimals();
49
47
  if (NO_DEICMAL_CURRENCIES.includes(options?.currency?.get().trimEnd() || "")) {
50
48
  // todo: more work needed here,
51
- num = num * 100;
52
- decimals = 0;
49
+ result = (source.get() * 100).toFixed(0).replace(".", ",");
50
+ }
51
+ else {
52
+ result = source.toFixed(source.getDecimals()).replace(".", ",");
53
53
  }
54
- result = num.toFixed(decimals).replace(".", ",");
55
54
  right = true;
56
55
  }
57
56
  else {
@@ -44,10 +44,10 @@ function templateFormatting(source, options) {
44
44
  }
45
45
  else if (source instanceof types_1.Packed) {
46
46
  if (options?.decimals) {
47
- text = source.get().toFixed(options.decimals);
47
+ text = source.toFixed(options.decimals);
48
48
  }
49
49
  else {
50
- text = source.get().toFixed(source.getDecimals());
50
+ text = source.toFixed(source.getDecimals());
51
51
  }
52
52
  }
53
53
  else {
@@ -11,10 +11,12 @@ export declare class Packed implements INumeric {
11
11
  });
12
12
  clone(): Packed;
13
13
  getQualifiedName(): string | undefined;
14
- private round;
14
+ private numberToScaled;
15
+ private stringToScaled;
15
16
  set(value: INumeric | number | string): this;
16
17
  getLength(): number;
17
18
  getDecimals(): number;
19
+ toFixed(decimals: number): string;
18
20
  clear(): void;
19
21
  get(): number;
20
22
  }
@@ -4,15 +4,42 @@ exports.Packed = void 0;
4
4
  const float_1 = require("./float");
5
5
  const throw_error_1 = require("../throw_error");
6
6
  const integer8_1 = require("./integer8");
7
- const digits = new RegExp(/^\s*-?\+?\d*\.?\d* *$/i);
7
+ const digits = new RegExp(/^\s*-?\+?\d*\.?\d*(?:E[+-]?\d+)? *$/i);
8
+ function pow10(exp) {
9
+ return 10n ** BigInt(exp);
10
+ }
11
+ // rescale a non-negative magnitude from fromScale to toScale decimal places,
12
+ // rounding half away from zero
13
+ function rescale(mag, fromScale, toScale) {
14
+ if (toScale >= fromScale) {
15
+ return mag * pow10(toScale - fromScale);
16
+ }
17
+ const div = pow10(fromScale - toScale);
18
+ const q = mag / div;
19
+ const r = mag % div;
20
+ return r * 2n >= div ? q + 1n : q;
21
+ }
22
+ // format a non-negative magnitude at the given scale as a decimal string
23
+ function formatMag(mag, scale) {
24
+ let s = mag.toString();
25
+ if (scale === 0) {
26
+ return s;
27
+ }
28
+ while (s.length <= scale) {
29
+ s = "0" + s;
30
+ }
31
+ const intPart = s.slice(0, s.length - scale);
32
+ const fracPart = s.slice(s.length - scale);
33
+ return intPart + "." + fracPart;
34
+ }
8
35
  class Packed {
9
- // todo: change this to bigint to get the proper precision? for larger than maxsafeint
36
+ // value holds the number scaled by 10^decimals, as a bigint, to keep full precision
10
37
  value;
11
38
  length;
12
39
  decimals;
13
40
  qualifiedName;
14
41
  constructor(input) {
15
- this.value = 0;
42
+ this.value = 0n;
16
43
  this.length = 666;
17
44
  if (input?.length) {
18
45
  this.length = input.length;
@@ -31,29 +58,60 @@ class Packed {
31
58
  getQualifiedName() {
32
59
  return this.qualifiedName;
33
60
  }
34
- round(value, places) {
35
- // @ts-ignore
36
- return +(Math.round(value + "e+" + places) + "e-" + places);
61
+ numberToScaled(value) {
62
+ const factor = Math.pow(10, this.decimals);
63
+ return BigInt(Math.round(value * factor));
64
+ }
65
+ stringToScaled(input) {
66
+ let str = input.trim();
67
+ let negative = false;
68
+ while (str.length > 0 && (str[0] === "+" || str[0] === "-")) {
69
+ if (str[0] === "-") {
70
+ negative = true;
71
+ }
72
+ str = str.slice(1);
73
+ }
74
+ if (/[eE]/.test(str)) {
75
+ // scientific notation, fall back to floating point parsing
76
+ return this.numberToScaled(parseFloat((negative ? "-" : "") + str));
77
+ }
78
+ let intPart = str;
79
+ let fracPart = "";
80
+ const dot = str.indexOf(".");
81
+ if (dot >= 0) {
82
+ intPart = str.slice(0, dot);
83
+ fracPart = str.slice(dot + 1);
84
+ }
85
+ if (intPart === "") {
86
+ intPart = "0";
87
+ }
88
+ const kept = fracPart.slice(0, this.decimals).padEnd(this.decimals, "0");
89
+ let scaled = BigInt(intPart + kept);
90
+ // round half up based on the first dropped fractional digit
91
+ if (fracPart.length > this.decimals && fracPart[this.decimals] >= "5") {
92
+ scaled += 1n;
93
+ }
94
+ return negative ? -scaled : scaled;
37
95
  }
38
96
  set(value) {
39
97
  if (typeof value === "number") {
40
- this.value = value;
98
+ this.value = this.numberToScaled(value);
41
99
  }
42
100
  else if (typeof value === "string") {
43
101
  if (value.trim().length === 0) {
44
- this.value = 0;
102
+ this.value = 0n;
45
103
  return this;
46
104
  }
47
105
  else if (digits.test(value) === false) {
48
106
  (0, throw_error_1.throwError)("CX_SY_CONVERSION_NO_NUMBER");
49
107
  }
50
- this.value = this.round(parseFloat(value), this.decimals);
108
+ this.value = this.stringToScaled(value);
51
109
  }
52
110
  else if (value instanceof integer8_1.Integer8) {
53
- this.value = Number(value.get());
111
+ this.value = value.get() * pow10(this.decimals);
54
112
  }
55
113
  else if (value instanceof float_1.Float) {
56
- this.value = this.round(value.getRaw(), this.decimals);
114
+ this.value = this.numberToScaled(value.getRaw());
57
115
  }
58
116
  else {
59
117
  this.set(value.get());
@@ -66,11 +124,20 @@ class Packed {
66
124
  getDecimals() {
67
125
  return this.decimals;
68
126
  }
127
+ // returns the value as a fixed decimal string with the given number of
128
+ // decimals, keeping full precision (unlike get() which is limited to double)
129
+ toFixed(decimals) {
130
+ const negative = this.value < 0n;
131
+ const mag = negative ? -this.value : this.value;
132
+ const scaled = rescale(mag, this.decimals, decimals);
133
+ const formatted = formatMag(scaled, decimals);
134
+ return (negative ? "-" : "") + formatted;
135
+ }
69
136
  clear() {
70
- this.value = 0;
137
+ this.value = 0n;
71
138
  }
72
139
  get() {
73
- return this.value;
140
+ return Number(this.value) / Math.pow(10, this.decimals);
74
141
  }
75
142
  }
76
143
  exports.Packed = Packed;
@@ -45,9 +45,9 @@ class String {
45
45
  this.value = value.getCharacter().trimEnd();
46
46
  }
47
47
  else if (value instanceof packed_1.Packed) {
48
- const lv_sign = value.get() >= 0 ? " " : "-";
49
- this.value = Math.abs(value.get()).toFixed(value.getDecimals());
50
- this.value += lv_sign;
48
+ const fixed = value.toFixed(value.getDecimals());
49
+ const lv_sign = fixed.startsWith("-") ? "-" : " ";
50
+ this.value = (fixed.startsWith("-") ? fixed.slice(1) : fixed) + lv_sign;
51
51
  }
52
52
  else if (value instanceof integer_1.Integer) {
53
53
  const lv_sign = value.get() >= 0 ? " " : "-";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abaplint/runtime",
3
- "version": "2.13.36",
3
+ "version": "2.13.38",
4
4
  "description": "Transpiler - Runtime",
5
5
  "main": "build/src/index.js",
6
6
  "typings": "build/src/index.d.ts",