ToBCD.mjs 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. /**
  2. * @author n1474335 [n1474335@gmail.com]
  3. * @copyright Crown Copyright 2017
  4. * @license Apache-2.0
  5. */
  6. import Operation from "../Operation";
  7. import Utils from "../Utils";
  8. import OperationError from "../errors/OperationError";
  9. import {ENCODING_SCHEME, ENCODING_LOOKUP, FORMAT} from "../lib/BCD";
  10. import BigNumber from "bignumber.js";
  11. /**
  12. * To BCD operation
  13. */
  14. class ToBCD extends Operation {
  15. /**
  16. * ToBCD constructor
  17. */
  18. constructor() {
  19. super();
  20. this.name = "To BCD";
  21. this.module = "Default";
  22. this.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";
  23. this.inputType = "BigNumber";
  24. this.outputType = "string";
  25. this.args = [
  26. {
  27. "name": "Scheme",
  28. "type": "option",
  29. "value": ENCODING_SCHEME
  30. },
  31. {
  32. "name": "Packed",
  33. "type": "boolean",
  34. "value": true
  35. },
  36. {
  37. "name": "Signed",
  38. "type": "boolean",
  39. "value": false
  40. },
  41. {
  42. "name": "Output format",
  43. "type": "option",
  44. "value": FORMAT
  45. }
  46. ];
  47. }
  48. /**
  49. * @param {BigNumber} input
  50. * @param {Object[]} args
  51. * @returns {string}
  52. */
  53. run(input, args) {
  54. if (input.isNaN())
  55. throw new OperationError("Invalid input");
  56. if (!input.integerValue(BigNumber.ROUND_DOWN).isEqualTo(input))
  57. throw new OperationError("Fractional values are not supported by BCD");
  58. const encoding = ENCODING_LOOKUP[args[0]],
  59. packed = args[1],
  60. signed = args[2],
  61. outputFormat = args[3];
  62. // Split input number up into separate digits
  63. const digits = input.toFixed().split("");
  64. if (digits[0] === "-" || digits[0] === "+") {
  65. digits.shift();
  66. }
  67. let nibbles = [];
  68. digits.forEach(d => {
  69. const n = parseInt(d, 10);
  70. nibbles.push(encoding[n]);
  71. });
  72. if (signed) {
  73. if (packed && digits.length % 2 === 0) {
  74. // If there are an even number of digits, we add a leading 0 so
  75. // that the sign nibble doesn't sit in its own byte, leading to
  76. // ambiguity around whether the number ends with a 0 or not.
  77. nibbles.unshift(encoding[0]);
  78. }
  79. nibbles.push(input > 0 ? 12 : 13);
  80. // 12 ("C") for + (credit)
  81. // 13 ("D") for - (debit)
  82. }
  83. let bytes = [];
  84. if (packed) {
  85. let encoded = 0,
  86. little = false;
  87. nibbles.forEach(n => {
  88. encoded ^= little ? n : (n << 4);
  89. if (little) {
  90. bytes.push(encoded);
  91. encoded = 0;
  92. }
  93. little = !little;
  94. });
  95. if (little) bytes.push(encoded);
  96. } else {
  97. bytes = nibbles;
  98. // Add null high nibbles
  99. nibbles = nibbles.map(n => {
  100. return [0, n];
  101. }).reduce((a, b) => {
  102. return a.concat(b);
  103. });
  104. }
  105. // Output
  106. switch (outputFormat) {
  107. case "Nibbles":
  108. return nibbles.map(n => {
  109. return n.toString(2).padStart(4, "0");
  110. }).join(" ");
  111. case "Bytes":
  112. return bytes.map(b => {
  113. return b.toString(2).padStart(8, "0");
  114. }).join(" ");
  115. case "Raw":
  116. default:
  117. return Utils.byteArrayToChars(bytes);
  118. }
  119. }
  120. }
  121. export default ToBCD;