picocalc 0.0.2 → 0.0.4

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.
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Roman A
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Roman A
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.d.mts CHANGED
@@ -1,3 +1,4 @@
1
+ import "bigint.bitlength";
1
2
  //#region src/lib/types.d.ts
2
3
  interface PrecisionOptions {
3
4
  readonly format?: "decimal" | "precise";
@@ -5,13 +6,19 @@ interface PrecisionOptions {
5
6
  readonly roundingMode?: "round" | "truncate";
6
7
  }
7
8
  //#endregion
9
+ //#region src/lib/errors.d.ts
10
+ declare class PicocalcError extends Error {
11
+ pos?: number;
12
+ constructor(name: string, message: string, pos?: number);
13
+ }
14
+ //#endregion
8
15
  //#region src/index.d.ts
9
16
  interface CalculateOptions extends PrecisionOptions {
10
17
  decimalSeparator?: "." | ",";
11
18
  }
12
19
  /**
13
- * The main entry point for the library.
14
- */
20
+ * The main entry point for the library.
21
+ */
15
22
  declare function calculate(expression: string, options?: CalculateOptions): string;
16
23
  //#endregion
17
- export { calculate };
24
+ export { PicocalcError, calculate };
package/dist/index.mjs CHANGED
@@ -1,15 +1,15 @@
1
1
  import "bigint.bitlength";
2
2
  //#region src/lib/errors.ts
3
- var GenericMathErrror = class extends Error {
3
+ var PicocalcError = class extends Error {
4
4
  pos;
5
5
  constructor(name, message, pos) {
6
- if (pos === void 0) super(`${name}: ${message}`);
7
- else super(`${name}: ${message} at position ${pos}`);
6
+ if (pos === void 0) super(message);
7
+ else super(`${message} at position ${pos}`);
8
8
  this.pos = pos;
9
9
  this.name = name;
10
10
  }
11
11
  };
12
- var InterpreterError = class extends GenericMathErrror {
12
+ var InterpreterError = class extends PicocalcError {
13
13
  constructor(message, pos) {
14
14
  super("InterpreterError", message, pos);
15
15
  }
@@ -403,7 +403,7 @@ function exponentiate(left, right, precision) {
403
403
  if (lN === lD && c === void 0) return ONE;
404
404
  if (rN === "OVERFLOW") return right;
405
405
  if (lN === -lD && c === void 0) {
406
- if (rN % 2n) return {
406
+ if (simplify(right).n % 2n) return {
407
407
  n: -1n,
408
408
  d: 1n
409
409
  };
@@ -552,8 +552,11 @@ const precedence = {
552
552
  SQRT_FN: 8,
553
553
  FACTORIAL: 10
554
554
  };
555
+ function isFunctionOp(op) {
556
+ return op === "ABS_FN" || op === "CEIL_FN" || op === "FLOOR_FN" || op === "SQRT_FN";
557
+ }
555
558
  function isUnaryOperation(op) {
556
- return op === "UNARY_PLUS" || op === "UNARY_MINUS" || op === "ABS_FN" || op === "CEIL_FN" || op === "FLOOR_FN" || op === "SQRT_FN";
559
+ return op === "UNARY_PLUS" || op === "UNARY_MINUS" || isFunctionOp(op);
557
560
  }
558
561
  /**
559
562
  * Maximum allowed precision
@@ -673,7 +676,7 @@ function evaluate(tokens, options = {}) {
673
676
  const pushOpWithPrecedence = (currentOp, pos) => {
674
677
  const isUnary = isUnaryOperation(currentOp);
675
678
  const isRightAssociative = currentOp === "EXP" || currentOp === "IMPLICIT_MUL";
676
- while (ops.length > 0 && !isUnary && (isRightAssociative ? precedence[ops[ops.length - 1]] > precedence[currentOp] : precedence[ops[ops.length - 1]] >= precedence[currentOp])) applyOp(pos);
679
+ while (ops.length > 0 && !isUnary && (isRightAssociative ? precedence[ops.at(-1)] > precedence[currentOp] : precedence[ops.at(-1)] >= precedence[currentOp])) applyOp(pos);
677
680
  ops.push(currentOp);
678
681
  };
679
682
  for (const token of tokens) switch (token.type) {
@@ -712,12 +715,13 @@ function evaluate(tokens, options = {}) {
712
715
  else values.push(getConst(token.id));
713
716
  break;
714
717
  case "LPAREN":
715
- ops.push("LPAREN");
718
+ case "ABS_OPEN":
719
+ ops.push(token.type);
716
720
  break;
717
721
  case "RPAREN": {
718
722
  let foundMatch = false;
719
723
  while (ops.length > 0) {
720
- if (ops[ops.length - 1] === "LPAREN") {
724
+ if (ops.at(-1) === "LPAREN") {
721
725
  foundMatch = true;
722
726
  break;
723
727
  }
@@ -725,14 +729,14 @@ function evaluate(tokens, options = {}) {
725
729
  }
726
730
  if (!foundMatch) throw new MismatchedParenthesisError(token.pos);
727
731
  ops.pop();
732
+ const nextOp = ops.at(-1);
733
+ if (!nextOp) break;
734
+ if (isFunctionOp(nextOp)) applyOp(token.pos);
728
735
  break;
729
736
  }
730
- case "ABS_OPEN":
731
- ops.push("ABS_OPEN");
732
- break;
733
737
  case "ABS_CLOSE": {
734
738
  while (ops.length > 0) {
735
- if (ops[ops.length - 1] === "ABS_OPEN") break;
739
+ if (ops.at(-1) === "ABS_OPEN") break;
736
740
  applyOp(token.pos);
737
741
  }
738
742
  ops.pop();
@@ -764,19 +768,11 @@ function evaluate(tokens, options = {}) {
764
768
  pushOpWithPrecedence("EXP", token.pos);
765
769
  break;
766
770
  case "UNARY_PLUS":
767
- pushOpWithPrecedence("UNARY_PLUS", token.pos);
768
- break;
769
771
  case "UNARY_MINUS":
770
- pushOpWithPrecedence("UNARY_MINUS", token.pos);
771
- break;
772
772
  case "IMPLICIT_MUL":
773
- pushOpWithPrecedence("IMPLICIT_MUL", token.pos);
774
- break;
775
773
  case "FACTORIAL":
776
- pushOpWithPrecedence("FACTORIAL", token.pos);
777
- break;
778
774
  case "MOD":
779
- pushOpWithPrecedence("MOD", token.pos);
775
+ pushOpWithPrecedence(token.type, token.pos);
780
776
  break;
781
777
  case "FUNC":
782
778
  switch (token.id) {
@@ -796,8 +792,8 @@ function evaluate(tokens, options = {}) {
796
792
  break;
797
793
  }
798
794
  while (ops.length > 0) {
799
- const top = ops[ops.length - 1];
800
- const lastPos = tokens[tokens.length - 1]?.pos ?? 0;
795
+ const top = ops.at(-1);
796
+ const lastPos = tokens.at(-1)?.pos ?? 0;
801
797
  if (top === "LPAREN") {
802
798
  ops.pop();
803
799
  continue;
@@ -810,14 +806,11 @@ function evaluate(tokens, options = {}) {
810
806
  }
811
807
  //#endregion
812
808
  //#region src/lib/lexer.ts
813
- var LexerError = class extends GenericMathErrror {
809
+ var LexerError = class extends PicocalcError {
814
810
  constructor(message, pos) {
815
811
  super("LexerError", message, pos);
816
812
  }
817
813
  };
818
- function isWhitespace(ch) {
819
- return ch === " ";
820
- }
821
814
  function isDigit(ch) {
822
815
  if (ch === void 0) return false;
823
816
  return ch >= "0" && ch <= "9";
@@ -830,42 +823,49 @@ function isAlpha(ch) {
830
823
  * Maximum allowed expression length
831
824
  */
832
825
  const MAX_EXPRESSION_LENGTH = 75e6;
826
+ const CHAR_MAP = {
827
+ "(": "LPAREN",
828
+ ")": "RPAREN",
829
+ "+": "PLUS",
830
+ "-": "MINUS",
831
+ "*": "MUL",
832
+ "/": "DIV",
833
+ "^": "POW",
834
+ "!": "FACTORIAL",
835
+ "|": "PIPE",
836
+ "%": "MOD"
837
+ };
833
838
  /**
834
839
  * Simple lexer (tokenizer).
835
840
  */
836
841
  function tokenize(expression, options = {}) {
837
842
  const { decimalSeparator = "." } = options;
838
843
  const tokens = [];
839
- let index = 0;
840
844
  const length = expression.length;
841
845
  if (length > MAX_EXPRESSION_LENGTH) throw new LexerError(`Expression length (${length}) exceeds the maximum allowed limit of ${MAX_EXPRESSION_LENGTH}`, 0);
842
- while (index < length) {
846
+ for (let index = 0; index < length; index++) {
843
847
  const ch = expression[index];
844
- const startPos = index;
845
- if (isWhitespace(ch)) {
846
- index++;
847
- continue;
848
- }
848
+ const pos = index;
849
+ if (ch === " ") continue;
849
850
  if (isDigit(ch) || ch === decimalSeparator) {
850
851
  let whole = "";
851
852
  let fraction = void 0;
852
853
  let exponent = void 0;
853
- const startIdx = index;
854
+ const consumeDigits = () => {
855
+ const start = index;
856
+ while (index < length && isDigit(expression[index])) index++;
857
+ return expression.slice(start, index);
858
+ };
854
859
  if (ch === decimalSeparator) {
855
860
  whole = "0";
856
861
  index++;
857
- const fracStart = index;
858
- while (index < length && isDigit(expression[index])) index++;
859
- if (index === fracStart) throw new LexerError("Expected digit after decimal separator", index);
860
- fraction = expression.slice(fracStart, index);
862
+ fraction = consumeDigits();
863
+ if (fraction.length === 0) throw new LexerError("Expected digit after decimal separator", index);
861
864
  } else {
862
- while (index < length && isDigit(expression[index])) index++;
863
- whole = expression.slice(startIdx, index);
865
+ whole = consumeDigits();
864
866
  if (index < length && expression[index] === decimalSeparator) {
865
867
  index++;
866
- const fracStart = index;
867
- while (index < length && isDigit(expression[index])) index++;
868
- fraction = expression.slice(fracStart, index);
868
+ fraction = consumeDigits();
869
869
  }
870
870
  }
871
871
  if (index < length && (expression[index] === "e" || expression[index] === "E")) {
@@ -878,115 +878,37 @@ function tokenize(expression, options = {}) {
878
878
  }
879
879
  if (lookahead < length && isDigit(expression[lookahead])) {
880
880
  index = lookahead;
881
- let expBuffer = sign;
882
- while (index < length && isDigit(expression[index])) {
883
- expBuffer += expression[index];
884
- index++;
885
- }
886
- exponent = expBuffer;
881
+ exponent = sign + consumeDigits();
887
882
  }
888
883
  }
889
884
  if (index < length && expression[index] === decimalSeparator) throw new LexerError("Invalid decimal number format", index);
885
+ index--;
890
886
  tokens.push({
891
887
  type: "NUMBER",
892
888
  whole,
893
889
  fraction,
894
890
  exponent,
895
- pos: startPos
891
+ pos
896
892
  });
897
893
  continue;
898
894
  }
899
895
  if (isAlpha(ch)) {
900
896
  let id = "";
901
897
  while (index < length && isAlpha(expression[index])) id += expression[index++];
898
+ index--;
902
899
  tokens.push({
903
900
  type: "IDENTIFIER",
904
901
  id,
905
- pos: startPos
906
- });
907
- continue;
908
- }
909
- if (ch === "(") {
910
- tokens.push({
911
- type: "LPAREN",
912
- pos: startPos
913
- });
914
- index++;
915
- continue;
916
- }
917
- if (ch === ")") {
918
- tokens.push({
919
- type: "RPAREN",
920
- pos: startPos
921
- });
922
- index++;
923
- continue;
924
- }
925
- if (ch === "+") {
926
- tokens.push({
927
- type: "PLUS",
928
- pos: startPos
929
- });
930
- index++;
931
- continue;
932
- }
933
- if (ch === "-") {
934
- tokens.push({
935
- type: "MINUS",
936
- pos: startPos
937
- });
938
- index++;
939
- continue;
940
- }
941
- if (ch === "*") {
942
- tokens.push({
943
- type: "MUL",
944
- pos: startPos
945
- });
946
- index++;
947
- continue;
948
- }
949
- if (ch === "/") {
950
- tokens.push({
951
- type: "DIV",
952
- pos: startPos
953
- });
954
- index++;
955
- continue;
956
- }
957
- if (ch === "^") {
958
- tokens.push({
959
- type: "POW",
960
- pos: startPos
902
+ pos
961
903
  });
962
- index++;
963
904
  continue;
964
905
  }
965
- if (ch === "!") {
966
- tokens.push({
967
- type: "FACTORIAL",
968
- pos: startPos
969
- });
970
- index++;
971
- continue;
972
- }
973
- if (ch === "|") {
974
- tokens.push({
975
- type: "PIPE",
976
- pos: startPos
977
- });
978
- index++;
979
- continue;
980
- }
981
- if (ch === "%") {
982
- tokens.push({
983
- type: "MOD",
984
- pos: startPos
985
- });
986
- index++;
987
- continue;
988
- }
989
- throw new LexerError(`Unexpected character '${ch}'`, index);
906
+ const type = CHAR_MAP[ch];
907
+ if (!type) throw new LexerError(`Unexpected character '${ch}'`, index);
908
+ tokens.push({
909
+ type,
910
+ pos
911
+ });
990
912
  }
991
913
  return tokens;
992
914
  }
@@ -1012,7 +934,7 @@ const symbolMap = {
1012
934
  const prettifyNumber = (t) => t.fraction ? `${t.whole}.${t.fraction}` : t.whole;
1013
935
  function getSym(t) {
1014
936
  if (t.type === "NUMBER") return prettifyNumber(t);
1015
- if (t.type === "IDENTIFIER") return t.id;
937
+ if (t.type === "IDENTIFIER" || t.type === "FUNC" || t.type === "CONST") return t.id;
1016
938
  return symbolMap[t.type];
1017
939
  }
1018
940
  //#endregion
@@ -1027,13 +949,10 @@ function isOperand(last) {
1027
949
  function isBinaryOperator(token) {
1028
950
  return token.type === "MUL" || token.type === "DIV" || token.type === "POW" || token.type === "MOD";
1029
951
  }
1030
- /**
1031
- * Helper: Does the current context allow a +/- to be unary?
1032
- */
1033
952
  function isUnaryContext(last) {
1034
953
  return !isOperand(last);
1035
954
  }
1036
- var ParserError = class extends GenericMathErrror {
955
+ var ParserError = class extends PicocalcError {
1037
956
  constructor(message, pos) {
1038
957
  super("ParserError", message, pos);
1039
958
  }
@@ -1049,15 +968,16 @@ var UnexpectedOperatorError = class extends ParserError {
1049
968
  }
1050
969
  };
1051
970
  function resolveIdentifier(token) {
971
+ const pos = token.pos;
1052
972
  if (token.id === "pi" || token.id === "e") return {
1053
973
  type: "CONST",
1054
974
  id: token.id,
1055
- pos: token.pos
975
+ pos
1056
976
  };
1057
977
  if (token.id === "abs" || token.id === "ceil" || token.id === "floor" || token.id === "sqrt") return {
1058
978
  type: "FUNC",
1059
979
  id: token.id,
1060
- pos: token.pos
980
+ pos
1061
981
  };
1062
982
  throw new ParserError(`Unknown identifier '${token.id}'`, token.pos);
1063
983
  }
@@ -1069,53 +989,47 @@ function parse(tokens) {
1069
989
  let token = tokens[i];
1070
990
  const prev = tokens[i - 1];
1071
991
  if (token.type === "IDENTIFIER") token = resolveIdentifier(token);
1072
- let prevParsed = result[result.length - 1];
992
+ const pos = token.pos;
993
+ const isPrevOperand = isOperand(result.at(-1));
1073
994
  if (token.type === "PIPE") {
1074
- if (absStack > 0 && isOperand(prevParsed)) {
995
+ if (absStack > 0 && isPrevOperand) {
1075
996
  result.push({
1076
997
  type: "ABS_CLOSE",
1077
- pos: token.pos
998
+ pos
1078
999
  });
1079
1000
  absStack--;
1080
- } else {
1081
- if (isOperand(prevParsed)) result.push({
1082
- type: "IMPLICIT_MUL",
1083
- pos: token.pos
1084
- });
1085
- result.push({
1086
- type: "ABS_OPEN",
1087
- pos: token.pos
1088
- });
1089
- absStack++;
1001
+ continue;
1090
1002
  }
1091
- continue;
1092
- }
1093
- if (isOperand(prevParsed)) {
1094
- if (token.type === "LPAREN" || token.type === "FUNC" || token.type === "NUMBER" || token.type === "CONST") result.push({
1003
+ if (isPrevOperand) result.push({
1095
1004
  type: "IMPLICIT_MUL",
1096
- pos: token.pos
1005
+ pos
1097
1006
  });
1007
+ result.push({
1008
+ type: "ABS_OPEN",
1009
+ pos
1010
+ });
1011
+ absStack++;
1012
+ continue;
1098
1013
  }
1099
- prevParsed = result[result.length - 1];
1100
- if (isBinaryOperator(token)) {
1101
- if (isUnaryContext(prevParsed)) throw new UnexpectedOperatorError(`Unexpected operator '${getSym(token)}'`, token.pos);
1102
- }
1103
- if (token.type === "FACTORIAL") {
1104
- if (!isOperand(prevParsed) && prevParsed?.type !== "RPAREN") throw new UnexpectedOperatorError("Unexpected factorial operator", token.pos);
1105
- }
1106
- if (token.type === "RPAREN") {
1107
- if (prevParsed && (prevParsed.type === "PLUS" || prevParsed.type === "MINUS" || isBinaryOperator(prevParsed))) throw new UnexpectedOperatorError(`Unexpected ')' after operator '${getSym(prevParsed)}'`, token.pos);
1108
- if (prevParsed?.type === "LPAREN") throw new UnexpectedOperatorError("Unexpected ')' after '('", token.pos);
1014
+ if ((token.type === "LPAREN" || token.type === "FUNC" || token.type === "NUMBER" || token.type === "CONST") && isPrevOperand) result.push({
1015
+ type: "IMPLICIT_MUL",
1016
+ pos
1017
+ });
1018
+ const prevParsed = result.at(-1);
1019
+ if (isBinaryOperator(token) && isUnaryContext(prevParsed)) throw new UnexpectedOperatorError(`Unexpected operator '${getSym(token)}'`, token.pos);
1020
+ if ((token.type === "FACTORIAL" || token.type === "RPAREN") && isUnaryContext(prevParsed)) {
1021
+ if (!prevParsed) throw new UnexpectedOperatorError(`Unexpected '${getSym(token)}' at the beginning of expression`, token.pos);
1022
+ throw new UnexpectedOperatorError(`Unexpected '${getSym(token)}' after '${getSym(prevParsed)}'`, token.pos);
1109
1023
  }
1110
1024
  if (prev && (prev.type === "NUMBER" || prev.type === "IDENTIFIER" && resolveIdentifier(prev).type === "CONST") && token.type === "NUMBER") throw new ParserError("Missing operator between numbers", token.pos);
1111
1025
  if ((token.type === "PLUS" || token.type === "MINUS") && isUnaryContext(prevParsed)) result.push({
1112
1026
  type: token.type === "PLUS" ? "UNARY_PLUS" : "UNARY_MINUS",
1113
- pos: token.pos
1027
+ pos
1114
1028
  });
1115
1029
  else result.push(token);
1116
1030
  }
1117
- if (absStack > 0) throw new ParserError("Unclosed absolute value '|'", result[result.length - 1]?.pos ?? 0);
1118
- const last = result[result.length - 1];
1031
+ const last = result.at(-1);
1032
+ if (absStack > 0) throw new ParserError("Unclosed absolute value '|'", last?.pos ?? 0);
1119
1033
  if (last && last.type !== "CONST" && (isBinaryOperator(last) || [
1120
1034
  "PLUS",
1121
1035
  "MINUS",
@@ -1132,16 +1046,15 @@ function parse(tokens) {
1132
1046
  function getConstantStr(coeff, c, e) {
1133
1047
  if (coeff === 0n) return "0";
1134
1048
  if (!c) return `${coeff}`;
1135
- const isOne = e && e.n === 1n && (e.d === void 0 || e.d === 1n);
1136
- if (!e || isOne) {
1137
- if (coeff === 1n) return c;
1138
- if (coeff === -1n) return `-${c}`;
1139
- return `${coeff}${c}`;
1049
+ let constantStr = c;
1050
+ if (e) {
1051
+ const num = e.n;
1052
+ const den = e.d ?? 1n;
1053
+ if (num !== den) {
1054
+ const expStr = den === 1n ? `${num}` : `(${num}/${den})`;
1055
+ constantStr = den === 2n * num ? `sqrt(${c})` : `${c}^${expStr}`;
1056
+ }
1140
1057
  }
1141
- const num = e.n;
1142
- const den = e.d ?? 1n;
1143
- const expStr = den === 1n ? `${num}` : `(${num}/${den})`;
1144
- const constantStr = den === 2n * num ? `sqrt(${c})` : `${c}^${expStr}`;
1145
1058
  if (coeff === 1n) return constantStr;
1146
1059
  if (coeff === -1n) return `-${constantStr}`;
1147
1060
  return `${coeff}${constantStr}`;
@@ -1165,46 +1078,17 @@ function formatPrecise(v) {
1165
1078
  */
1166
1079
  function formatResult(v, options = {}) {
1167
1080
  const { n, d } = v;
1168
- if (d === 0n) return "NaN";
1169
1081
  if (options.format === "precise") return formatPrecise(v);
1170
1082
  const { maxDecimals = 30, roundingMode = "round" } = options;
1171
1083
  const isNegative = n < 0;
1172
- const absN = isNegative ? -n : n;
1173
- let integerPart = (absN / d).toString();
1174
- let remainder = absN % d;
1175
- if (remainder === 0n) {
1176
- const res = (isNegative ? "-" : "") + integerPart;
1177
- return res === "-0" ? "0" : res;
1178
- }
1179
- let fractionalPart = "";
1180
- let count = 0;
1181
- const targetLength = maxDecimals + 1;
1182
- while (remainder !== 0n && count < targetLength) {
1183
- remainder *= 10n;
1184
- fractionalPart += (remainder / d).toString();
1185
- remainder %= d;
1186
- count++;
1187
- }
1188
- const nexDigitStr = fractionalPart[maxDecimals];
1189
- if (nexDigitStr) {
1190
- const nextDigit = parseInt(nexDigitStr, 10);
1191
- fractionalPart = fractionalPart.slice(0, maxDecimals);
1192
- if (roundingMode === "round" && nextDigit >= 5) {
1193
- const incremented = (BigInt(fractionalPart) + 1n).toString();
1194
- if (incremented.length > fractionalPart.length) {
1195
- integerPart = (BigInt(integerPart) + 1n).toString();
1196
- fractionalPart = incremented.slice(1);
1197
- } else fractionalPart = incremented.padStart(fractionalPart.length, "0");
1198
- }
1199
- }
1200
- let lastNonZero = -1;
1201
- for (let i = fractionalPart.length - 1; i >= 0; i--) if (fractionalPart[i] !== "0") {
1202
- lastNonZero = i;
1203
- break;
1204
- }
1205
- const finalFraction = lastNonZero === -1 ? "" : fractionalPart.slice(0, lastNonZero + 1);
1206
- const result = finalFraction === "" ? integerPart : `${integerPart}.${finalFraction}`;
1207
- return (isNegative && result !== "0" ? "-" : "") + result;
1084
+ const factor = 10n ** BigInt(maxDecimals);
1085
+ let resultStr = (((isNegative ? -n : n) * factor + (roundingMode === "round" ? d / 2n : 0n)) / d).toString();
1086
+ if (resultStr.length <= maxDecimals) resultStr = resultStr.padStart(maxDecimals + 1, "0");
1087
+ const splitIdx = resultStr.length - maxDecimals;
1088
+ const integerPart = resultStr.slice(0, splitIdx);
1089
+ const fractionalPart = resultStr.slice(splitIdx).replace(/0+$/, "");
1090
+ const finalResult = fractionalPart ? `${integerPart}.${fractionalPart}` : integerPart;
1091
+ return (isNegative && finalResult !== "0" ? "-" : "") + finalResult;
1208
1092
  }
1209
1093
  //#endregion
1210
1094
  //#region src/index.ts
@@ -1215,4 +1099,4 @@ function calculate(expression, options) {
1215
1099
  return formatResult(evaluate(parse(tokenize(expression, options)), options), options);
1216
1100
  }
1217
1101
  //#endregion
1218
- export { calculate };
1102
+ export { PicocalcError, calculate };
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "picocalc",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
7
- "url": "git+https://github.com/picocalc/math-parser.git"
7
+ "url": "git+https://github.com/picocalc/picocalc.git"
8
8
  },
9
9
  "files": [
10
10
  "dist"
@@ -30,12 +30,12 @@
30
30
  "bigint.bitlength": "0.2.0"
31
31
  },
32
32
  "devDependencies": {
33
- "@gameroman/config": "0.2.2",
33
+ "@gameroman/config": "0.2.5",
34
34
  "@types/bun": "1.3.14",
35
- "oxfmt": "0.57.0",
36
- "oxlint": "1.72.0",
35
+ "oxfmt": "0.58.0",
36
+ "oxlint": "1.73.0",
37
37
  "oxlint-tsgolint": "0.24.0",
38
- "tsdown": "0.22.3",
38
+ "tsdown": "0.22.7",
39
39
  "typescript": "6.0.3"
40
40
  }
41
41
  }