Skip to content

Commit c773edc

Browse files
committed
Added BCD operations with tests
1 parent bcaef8b commit c773edc

File tree

6 files changed

+382
-1
lines changed

6 files changed

+382
-1
lines changed

Diff for: src/core/config/Categories.js

+2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ const Categories = [
4646
"From Base58",
4747
"To Base",
4848
"From Base",
49+
"To BCD",
50+
"From BCD",
4951
"To HTML Entity",
5052
"From HTML Entity",
5153
"URL Encode",

Diff for: src/core/config/OperationConfig.js

+59
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import FlowControl from "../FlowControl.js";
22
import Base from "../operations/Base.js";
33
import Base58 from "../operations/Base58.js";
44
import Base64 from "../operations/Base64.js";
5+
import BCD from "../operations/BCD.js";
56
import BitwiseOp from "../operations/BitwiseOp.js";
67
import ByteRepr from "../operations/ByteRepr.js";
78
import CharEnc from "../operations/CharEnc.js";
@@ -3507,6 +3508,64 @@ const OperationConfig = {
35073508
}
35083509
]
35093510
},
3511+
"From BCD": {
3512+
description: "Binary-Coded Decimal (BCD) is a class of binary encodings of decimal numbers where each decimal digit is represented by a fixed number of bits, usually four or eight. Special bit patterns are sometimes used for a sign.",
3513+
run: BCD.runFromBCD,
3514+
inputType: "string",
3515+
outputType: "number",
3516+
args: [
3517+
{
3518+
name: "Scheme",
3519+
type: "option",
3520+
value: BCD.ENCODING_SCHEME
3521+
},
3522+
{
3523+
name: "Packed",
3524+
type: "boolean",
3525+
value: true
3526+
},
3527+
{
3528+
name: "Signed",
3529+
type: "boolean",
3530+
value: false
3531+
},
3532+
{
3533+
name: "Input format",
3534+
type: "option",
3535+
value: BCD.FORMAT
3536+
}
3537+
]
3538+
3539+
},
3540+
"To BCD": {
3541+
description: "Binary-Coded Decimal (BCD) is a class of binary encodings of decimal numbers where each decimal digit is represented by a fixed number of bits, usually four or eight. Special bit patterns are sometimes used for a sign",
3542+
run: BCD.runToBCD,
3543+
inputType: "number",
3544+
outputType: "string",
3545+
args: [
3546+
{
3547+
name: "Scheme",
3548+
type: "option",
3549+
value: BCD.ENCODING_SCHEME
3550+
},
3551+
{
3552+
name: "Packed",
3553+
type: "boolean",
3554+
value: true
3555+
},
3556+
{
3557+
name: "Signed",
3558+
type: "boolean",
3559+
value: false
3560+
},
3561+
{
3562+
name: "Output format",
3563+
type: "option",
3564+
value: BCD.FORMAT
3565+
}
3566+
]
3567+
3568+
},
35103569
};
35113570

35123571
export default OperationConfig;

Diff for: src/core/operations/BCD.js

+214
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
import Utils from "../Utils.js";
2+
3+
4+
/**
5+
* Binary-Coded Decimal operations.
6+
*
7+
* @author n1474335 [[email protected]]
8+
* @copyright Crown Copyright 2017
9+
* @license Apache-2.0
10+
*
11+
* @namespace
12+
*/
13+
const BCD = {
14+
15+
/**
16+
* @constant
17+
* @default
18+
*/
19+
ENCODING_SCHEME: [
20+
"8 4 2 1",
21+
"7 4 2 1",
22+
"4 2 2 1",
23+
"2 4 2 1",
24+
"8 4 -2 -1",
25+
"Excess-3",
26+
"IBM 8 4 2 1",
27+
],
28+
29+
/**
30+
* Lookup table for the binary value of each digit representation.
31+
*
32+
* I wrote a very nice algorithm to generate 8 4 2 1 encoding programatically,
33+
* but unfortunately it's much easier (if less elegant) to use lookup tables
34+
* when supporting multiple encoding schemes.
35+
*
36+
* "Practicality beats purity" - PEP 20
37+
*
38+
* In some schemes it is possible to represent the same value in multiple ways.
39+
* For instance, in 4 2 2 1 encoding, 0100 and 0010 both represent 2. Support
40+
* has not yet been added for this.
41+
*
42+
* @constant
43+
*/
44+
ENCODING_LOOKUP: {
45+
"8 4 2 1": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
46+
"7 4 2 1": [0, 1, 2, 3, 4, 5, 6, 8, 9, 10],
47+
"4 2 2 1": [0, 1, 4, 5, 8, 9, 12, 13, 14, 15],
48+
"2 4 2 1": [0, 1, 2, 3, 4, 11, 12, 13, 14, 15],
49+
"8 4 -2 -1": [0, 7, 6, 5, 4, 11, 10, 9, 8, 15],
50+
"Excess-3": [3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
51+
"IBM 8 4 2 1": [10, 1, 2, 3, 4, 5, 6, 7, 8, 9],
52+
},
53+
54+
/**
55+
* @default
56+
* @constant
57+
*/
58+
FORMAT: ["Nibbles", "Bytes", "Raw"],
59+
60+
61+
/**
62+
* To BCD operation.
63+
*
64+
* @param {number} input
65+
* @param {Object[]} args
66+
* @returns {string}
67+
*/
68+
runToBCD: function(input, args) {
69+
if (isNaN(input))
70+
return "Invalid input";
71+
if (Math.floor(input) !== input)
72+
return "Fractional values are not supported by BCD";
73+
74+
const encoding = BCD.ENCODING_LOOKUP[args[0]],
75+
packed = args[1],
76+
signed = args[2],
77+
outputFormat = args[3];
78+
79+
// Split input number up into separate digits
80+
const digits = input.toString().split("");
81+
82+
if (digits[0] === "-" || digits[0] === "+") {
83+
digits.shift();
84+
}
85+
86+
let nibbles = [];
87+
88+
digits.forEach(d => {
89+
const n = parseInt(d, 10);
90+
nibbles.push(encoding[n]);
91+
});
92+
93+
if (signed) {
94+
if (packed && digits.length % 2 === 0) {
95+
// If there are an even number of digits, we add a leading 0 so
96+
// that the sign nibble doesn't sit in its own byte, leading to
97+
// ambiguity around whether the number ends with a 0 or not.
98+
nibbles.unshift(encoding[0]);
99+
}
100+
101+
nibbles.push(input > 0 ? 12 : 13);
102+
// 12 ("C") for + (credit)
103+
// 13 ("D") for - (debit)
104+
}
105+
106+
let bytes = [];
107+
108+
if (packed) {
109+
let encoded = 0,
110+
little = false;
111+
112+
nibbles.forEach(n => {
113+
encoded ^= little ? n : (n << 4);
114+
if (little) {
115+
bytes.push(encoded);
116+
encoded = 0;
117+
}
118+
little = !little;
119+
});
120+
121+
if (little) bytes.push(encoded);
122+
} else {
123+
bytes = nibbles;
124+
125+
// Add null high nibbles
126+
nibbles = nibbles.map(n => {
127+
return [0, n];
128+
}).reduce((a, b) => {
129+
return a.concat(b);
130+
});
131+
}
132+
133+
// Output
134+
switch (outputFormat) {
135+
case "Nibbles":
136+
return nibbles.map(n => {
137+
return Utils.padLeft(n.toString(2), 4);
138+
}).join(" ");
139+
case "Bytes":
140+
return bytes.map(b => {
141+
return Utils.padLeft(b.toString(2), 8);
142+
}).join(" ");
143+
case "Raw":
144+
default:
145+
return Utils.byteArrayToChars(bytes);
146+
}
147+
},
148+
149+
150+
/**
151+
* From BCD operation.
152+
*
153+
* @param {string} input
154+
* @param {Object[]} args
155+
* @returns {number}
156+
*/
157+
runFromBCD: function(input, args) {
158+
const encoding = BCD.ENCODING_LOOKUP[args[0]],
159+
packed = args[1],
160+
signed = args[2],
161+
inputFormat = args[3];
162+
163+
let nibbles = [],
164+
output = "",
165+
byteArray;
166+
167+
// Normalise the input
168+
switch (inputFormat) {
169+
case "Nibbles":
170+
case "Bytes":
171+
input = input.replace(/\s/g, "");
172+
for (let i = 0; i < input.length; i += 4) {
173+
nibbles.push(parseInt(input.substr(i, 4), 2));
174+
}
175+
break;
176+
case "Raw":
177+
default:
178+
byteArray = Utils.strToByteArray(input);
179+
byteArray.forEach(b => {
180+
nibbles.push(b >>> 4);
181+
nibbles.push(b & 15);
182+
});
183+
break;
184+
}
185+
186+
if (!packed) {
187+
// Discard each high nibble
188+
for (let i = 0; i < nibbles.length; i++) {
189+
nibbles.splice(i, 1);
190+
}
191+
}
192+
193+
if (signed) {
194+
const sign = nibbles.pop();
195+
if (sign === 13 ||
196+
sign === 11) {
197+
// Negative
198+
output += "-";
199+
}
200+
}
201+
202+
nibbles.forEach(n => {
203+
if (isNaN(n)) throw "Invalid input";
204+
let val = encoding.indexOf(n);
205+
if (val < 0) throw `Value ${Utils.bin(n, 4)} not in encoding scheme`;
206+
output += val.toString();
207+
});
208+
209+
return parseInt(output, 10);
210+
},
211+
212+
};
213+
214+
export default BCD;

Diff for: src/web/html/index.html

+3-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@
3535
"use strict";
3636

3737
// Load theme before the preloader is shown
38-
document.querySelector(":root").className = JSON.parse(localStorage.getItem("options")).theme;
38+
try {
39+
document.querySelector(":root").className = JSON.parse(localStorage.getItem("options")).theme;
40+
} catch (e) {}
3941

4042
// Define loading messages
4143
const loadingMsgs = [

Diff for: test/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import "babel-polyfill";
1212

1313
import TestRegister from "./TestRegister.js";
1414
import "./tests/operations/Base58.js";
15+
import "./tests/operations/BCD.js";
1516
import "./tests/operations/ByteRepr.js";
1617
import "./tests/operations/CharEnc.js";
1718
import "./tests/operations/Cipher.js";

0 commit comments

Comments
 (0)