number-parser.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. /**
  2. * Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  6. *
  7. * based in part on https://github.com/dsehnal/CIFTools.js
  8. */
  9. /**
  10. * Efficient integer and float parsers.
  11. *
  12. * For the purposes of parsing numbers from the mmCIF data representations,
  13. * up to 4 times faster than JS parseInt/parseFloat.
  14. */
  15. export function parseIntSkipLeadingWhitespace(str: string, start: number, end: number) {
  16. while (start < end && str.charCodeAt(start) === 32) start++;
  17. return parseInt(str, start, end);
  18. }
  19. export function parseInt(str: string, start: number, end: number) {
  20. let _start = start, ret = 0, neg = 1;
  21. if (str.charCodeAt(_start) === 45 /* - */) {
  22. neg = -1;
  23. ++_start;
  24. } else if (str.charCodeAt(_start) === 43 /* + */) {
  25. ++_start;
  26. }
  27. for (; _start < end; _start++) {
  28. const c = str.charCodeAt(_start) - 48;
  29. if (c > 9 || c < 0) return (neg * ret) | 0;
  30. else ret = (10 * ret + c) | 0;
  31. }
  32. return neg * ret;
  33. }
  34. function parseScientific(main: number, str: string, start: number, end: number) {
  35. // handle + in '1e+1' separately.
  36. if (str.charCodeAt(start) === 43 /* + */) start++;
  37. return main * Math.pow(10.0, parseInt(str, start, end));
  38. }
  39. export function parseFloatSkipLeadingWhitespace(str: string, start: number, end: number) {
  40. while (start < end && str.charCodeAt(start) === 32) start++;
  41. return parseFloat(str, start, end);
  42. }
  43. export function parseFloat(str: string, start: number, end: number) {
  44. let _start = start, neg = 1.0, ret = 0.0, point = 0.0, div = 1.0;
  45. if (str.charCodeAt(_start) === 45 /* - */) {
  46. neg = -1;
  47. ++_start;
  48. } else if (str.charCodeAt(_start) === 43 /* + */) {
  49. ++_start;
  50. }
  51. while (_start < end) {
  52. let c = str.charCodeAt(_start) - 48;
  53. if (c >= 0 && c < 10) {
  54. ret = ret * 10 + c;
  55. ++_start;
  56. } else if (c === -2) { // .
  57. ++_start;
  58. while (_start < end) {
  59. c = str.charCodeAt(_start) - 48;
  60. if (c >= 0 && c < 10) {
  61. point = 10.0 * point + c;
  62. div = 10.0 * div;
  63. ++_start;
  64. } else if (c === 53 || c === 21) { // 'e'/'E'
  65. return parseScientific(neg * (ret + point / div), str, _start + 1, end);
  66. } else {
  67. return neg * (ret + point / div);
  68. }
  69. }
  70. return neg * (ret + point / div);
  71. } else if (c === 53 || c === 21) { // 'e'/'E'
  72. return parseScientific(neg * ret, str, _start + 1, end);
  73. } else {
  74. break;
  75. }
  76. }
  77. return neg * ret;
  78. }
  79. export const enum NumberType {
  80. Int,
  81. Float,
  82. Scientific,
  83. NaN
  84. }
  85. function isInt(str: string, start: number, end: number) {
  86. if (str.charCodeAt(start) === 45 /* - */) { start++; }
  87. for (; start < end; start++) {
  88. const c = str.charCodeAt(start) - 48;
  89. if (c > 9 || c < 0) return false;
  90. }
  91. return true;
  92. }
  93. // TODO: check for "scientific integers?"
  94. function getNumberTypeScientific(str: string, start: number, end: number) {
  95. // handle + in '1e+1' separately.
  96. if (str.charCodeAt(start) === 43 /* + */) start++;
  97. return isInt(str, start, end) ? NumberType.Scientific : NumberType.NaN;
  98. }
  99. /** The whole range must match, otherwise returns NaN */
  100. export function getNumberType(str: string): NumberType {
  101. let start = 0;
  102. const end = str.length;
  103. if (str.charCodeAt(start) === 45) { // -
  104. ++start;
  105. }
  106. // string is . or -.
  107. if (str.charCodeAt(start) === 46 && end - start === 1) {
  108. return NumberType.NaN;
  109. }
  110. while (start < end) {
  111. let c = str.charCodeAt(start) - 48;
  112. if (c >= 0 && c < 10) {
  113. ++start;
  114. } else if (c === -2) { // .
  115. ++start;
  116. let hasDigit = false;
  117. while (start < end) {
  118. c = str.charCodeAt(start) - 48;
  119. if (c >= 0 && c < 10) {
  120. hasDigit = true;
  121. ++start;
  122. } else if (c === 53 || c === 21) { // 'e'/'E'
  123. return getNumberTypeScientific(str, start + 1, end);
  124. } else {
  125. return NumberType.NaN;
  126. }
  127. }
  128. return hasDigit ? NumberType.Float : NumberType.Int;
  129. } else if (c === 53 || c === 21) { // 'e'/'E'
  130. if (start === 0 || start === 1 && str.charCodeAt(0) === 45) {
  131. return NumberType.NaN; // string starts with e/E or -e/-E
  132. }
  133. return getNumberTypeScientific(str, start + 1, end);
  134. } else {
  135. break;
  136. }
  137. }
  138. return start === end ? NumberType.Int : NumberType.NaN;
  139. }