ts-fsrs 5.2.3 → 5.3.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ # ts-fsrs
2
+
3
+ ## 5.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#315](https://github.com/open-spaced-repetition/ts-fsrs/pull/315) [`40e712e`](https://github.com/open-spaced-repetition/ts-fsrs/commit/40e712ebf229997154682294f40436bf1524022d) Thanks [@moaaz-ae](https://github.com/moaaz-ae)! - fix: prevent unexpected skipping of learning steps when Rating.Good is selected in State.Learning (#311)
8
+
9
+ ## 5.3.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [#266](https://github.com/open-spaced-repetition/ts-fsrs/pull/266) [`17fb34d`](https://github.com/open-spaced-repetition/ts-fsrs/commit/17fb34d849c66f2035c5a239e2dfd64ed40055c9) Thanks [@ishiko732](https://github.com/ishiko732)! - Non-decreasing SInc(Hard) in same-day reviews — sync with fsrs-rs#376 changes
14
+
15
+ ### Patch Changes
16
+
17
+ - [#314](https://github.com/open-spaced-repetition/ts-fsrs/pull/314) [`87f94f2`](https://github.com/open-spaced-repetition/ts-fsrs/commit/87f94f277f7030a449490cef943e1aebe1585b64) Thanks [@user1823](https://github.com/user1823)! - Performance improvements
package/dist/index.cjs CHANGED
@@ -54,6 +54,9 @@ class TypeConvert {
54
54
  throw new Error(`Invalid state:[${value}]`);
55
55
  }
56
56
  static time(value) {
57
+ if (value instanceof Date) {
58
+ return value;
59
+ }
57
60
  const date = new Date(value);
58
61
  if (typeof value === "object" && value !== null && !Number.isNaN(Date.parse(value) || +date)) {
59
62
  return date;
@@ -80,15 +83,19 @@ class TypeConvert {
80
83
  }
81
84
  }
82
85
 
86
+ /* istanbul ignore next -- @preserve */
83
87
  Date.prototype.scheduler = function(t, isDay) {
84
88
  return date_scheduler(this, t, isDay);
85
89
  };
90
+ /* istanbul ignore next -- @preserve */
86
91
  Date.prototype.diff = function(pre, unit) {
87
92
  return date_diff(this, pre, unit);
88
93
  };
94
+ /* istanbul ignore next -- @preserve */
89
95
  Date.prototype.format = function() {
90
96
  return formatDate(this);
91
97
  };
98
+ /* istanbul ignore next -- @preserve */
92
99
  Date.prototype.dueFormat = function(last_review, unit, timeUnit) {
93
100
  return show_diff_message(this, last_review, unit, timeUnit);
94
101
  };
@@ -148,12 +155,15 @@ function show_diff_message(due, last_review, unit, timeUnit = TIMEUNITFORMAT) {
148
155
  }
149
156
  return `${Math.floor(diff)}${unit ? timeUnit[i] : ""}`;
150
157
  }
158
+ /* istanbul ignore next -- @preserve */
151
159
  function fixDate(value) {
152
160
  return TypeConvert.time(value);
153
161
  }
162
+ /* istanbul ignore next -- @preserve */
154
163
  function fixState(value) {
155
164
  return TypeConvert.state(value);
156
165
  }
166
+ /* istanbul ignore next -- @preserve */
157
167
  function fixRating(value) {
158
168
  return TypeConvert.rating(value);
159
169
  }
@@ -197,6 +207,10 @@ function get_fuzz_range(interval, elapsed_days, maximum_interval) {
197
207
  function clamp(value, min, max) {
198
208
  return Math.min(Math.max(value, min), max);
199
209
  }
210
+ function roundTo(num, decimals) {
211
+ const factor = 10 ** decimals;
212
+ return Math.round(num * factor) / factor;
213
+ }
200
214
  function dateDiffInDays(last, cur) {
201
215
  const utc1 = Date.UTC(
202
216
  last.getUTCFullYear(),
@@ -469,7 +483,7 @@ function alea(seed) {
469
483
  return prng;
470
484
  }
471
485
 
472
- const version="5.2.3";
486
+ const version="5.3.1";
473
487
 
474
488
  const default_request_retention = 0.9;
475
489
  const default_maximum_interval = 36500;
@@ -643,11 +657,11 @@ function createEmptyCard(now, afterHandler) {
643
657
  const computeDecayFactor = (decayOrParams) => {
644
658
  const decay = typeof decayOrParams === "number" ? -decayOrParams : -decayOrParams[20];
645
659
  const factor = Math.exp(Math.pow(decay, -1) * Math.log(0.9)) - 1;
646
- return { decay, factor: +factor.toFixed(8) };
660
+ return { decay, factor: roundTo(factor, 8) };
647
661
  };
648
662
  function forgetting_curve(decayOrParams, elapsed_days, stability) {
649
663
  const { decay, factor } = computeDecayFactor(decayOrParams);
650
- return +Math.pow(1 + factor * elapsed_days / stability, decay).toFixed(8);
664
+ return roundTo(Math.pow(1 + factor * elapsed_days / stability, decay), 8);
651
665
  }
652
666
  class FSRSAlgorithm {
653
667
  param;
@@ -681,7 +695,7 @@ class FSRSAlgorithm {
681
695
  throw new Error("Requested retention rate should be in the range (0,1]");
682
696
  }
683
697
  const { decay, factor } = computeDecayFactor(this.param.w);
684
- return +((Math.pow(request_retention, 1 / decay) - 1) / factor).toFixed(8);
698
+ return roundTo((Math.pow(request_retention, 1 / decay) - 1) / factor, 8);
685
699
  }
686
700
  /**
687
701
  * Get the parameters of the algorithm.
@@ -748,8 +762,9 @@ class FSRSAlgorithm {
748
762
  * @return {number} Difficulty $$D \in [1,10]$$
749
763
  */
750
764
  init_difficulty(g) {
751
- const d = this.param.w[4] - Math.exp((g - 1) * this.param.w[5]) + 1;
752
- return +d.toFixed(8);
765
+ const w = this.param.w;
766
+ const d = w[4] - Math.exp((g - 1) * w[5]) + 1;
767
+ return roundTo(d, 8);
753
768
  }
754
769
  /**
755
770
  * If fuzzing is disabled or ivl is less than 2.5, it returns the original interval.
@@ -784,7 +799,7 @@ class FSRSAlgorithm {
784
799
  * @see https://github.com/open-spaced-repetition/fsrs4anki/issues/697
785
800
  */
786
801
  linear_damping(delta_d, old_d) {
787
- return +(delta_d * (10 - old_d) / 9).toFixed(8);
802
+ return roundTo(delta_d * (10 - old_d) / 9, 8);
788
803
  }
789
804
  /**
790
805
  * The formula used is :
@@ -812,9 +827,8 @@ class FSRSAlgorithm {
812
827
  * @return {number} difficulty
813
828
  */
814
829
  mean_reversion(init, current) {
815
- return +(this.param.w[7] * init + (1 - this.param.w[7]) * current).toFixed(
816
- 8
817
- );
830
+ const w = this.param.w;
831
+ return roundTo(w[7] * init + (1 - w[7]) * current, 8);
818
832
  }
819
833
  /**
820
834
  * The formula used is :
@@ -826,13 +840,17 @@ class FSRSAlgorithm {
826
840
  * @return {number} S^\prime_r new stability after recall
827
841
  */
828
842
  next_recall_stability(d, s, r, g) {
829
- const hard_penalty = Rating.Hard === g ? this.param.w[15] : 1;
830
- const easy_bound = Rating.Easy === g ? this.param.w[16] : 1;
831
- return +clamp(
832
- s * (1 + Math.exp(this.param.w[8]) * (11 - d) * Math.pow(s, -this.param.w[9]) * (Math.exp((1 - r) * this.param.w[10]) - 1) * hard_penalty * easy_bound),
833
- S_MIN,
834
- 36500
835
- ).toFixed(8);
843
+ const w = this.param.w;
844
+ const hard_penalty = Rating.Hard === g ? w[15] : 1;
845
+ const easy_bound = Rating.Easy === g ? w[16] : 1;
846
+ return roundTo(
847
+ clamp(
848
+ s * (1 + Math.exp(w[8]) * (11 - d) * Math.pow(s, -w[9]) * (Math.exp((1 - r) * w[10]) - 1) * hard_penalty * easy_bound),
849
+ S_MIN,
850
+ 36500
851
+ ),
852
+ 8
853
+ );
836
854
  }
837
855
  /**
838
856
  * The formula used is :
@@ -845,11 +863,15 @@ class FSRSAlgorithm {
845
863
  * @return {number} S^\prime_f new stability after forgetting
846
864
  */
847
865
  next_forget_stability(d, s, r) {
848
- return +clamp(
849
- this.param.w[11] * Math.pow(d, -this.param.w[12]) * (Math.pow(s + 1, this.param.w[13]) - 1) * Math.exp((1 - r) * this.param.w[14]),
850
- S_MIN,
851
- 36500
852
- ).toFixed(8);
866
+ const w = this.param.w;
867
+ return roundTo(
868
+ clamp(
869
+ w[11] * Math.pow(d, -w[12]) * (Math.pow(s + 1, w[13]) - 1) * Math.exp((1 - r) * w[14]),
870
+ S_MIN,
871
+ 36500
872
+ ),
873
+ 8
874
+ );
853
875
  }
854
876
  /**
855
877
  * The formula used is :
@@ -858,9 +880,10 @@ class FSRSAlgorithm {
858
880
  * @param {Grade} g Grade (Rating[0.again,1.hard,2.good,3.easy])
859
881
  */
860
882
  next_short_term_stability(s, g) {
861
- const sinc = Math.pow(s, -this.param.w[19]) * Math.exp(this.param.w[17] * (g - 3 + this.param.w[18]));
862
- const maskedSinc = g >= 3 ? Math.max(sinc, 1) : sinc;
863
- return +clamp(s * maskedSinc, S_MIN, 36500).toFixed(8);
883
+ const w = this.param.w;
884
+ const sinc = Math.pow(s, -w[19]) * Math.exp(w[17] * (g - 3 + w[18]));
885
+ const maskedSinc = g >= Rating.Hard ? Math.max(sinc, 1) : sinc;
886
+ return roundTo(clamp(s * maskedSinc, S_MIN, 36500), 8);
864
887
  }
865
888
  /**
866
889
  * The formula used is :
@@ -876,9 +899,10 @@ class FSRSAlgorithm {
876
899
  * @param memory_state - The current state of memory, which can be null.
877
900
  * @param t - The time elapsed since the last review.
878
901
  * @param {Rating} g Grade (Rating[0.Manual,1.Again,2.Hard,3.Good,4.Easy])
902
+ * @param r - Optional retrievability value. If not provided, it will be calculated.
879
903
  * @returns The next state of memory with updated difficulty and stability.
880
904
  */
881
- next_state(memory_state, t, g) {
905
+ next_state(memory_state, t, g, r) {
882
906
  const { difficulty: d, stability: s } = memory_state ?? {
883
907
  difficulty: 0,
884
908
  stability: 0
@@ -906,22 +930,22 @@ class FSRSAlgorithm {
906
930
  `Invalid memory state { difficulty: ${d}, stability: ${s} }`
907
931
  );
908
932
  }
909
- const r = this.forgetting_curve(t, s);
910
- const s_after_success = this.next_recall_stability(d, s, r, g);
911
- const s_after_fail = this.next_forget_stability(d, s, r);
912
- const s_after_short_term = this.next_short_term_stability(s, g);
913
- let new_s = s_after_success;
914
- if (g === 1) {
933
+ const w = this.param.w;
934
+ r = typeof r === "number" ? r : this.forgetting_curve(t, s);
935
+ let new_s;
936
+ if (t === 0 && this.param.enable_short_term) {
937
+ new_s = this.next_short_term_stability(s, g);
938
+ } else if (g === 1) {
939
+ const s_after_fail = this.next_forget_stability(d, s, r);
915
940
  let [w_17, w_18] = [0, 0];
916
941
  if (this.param.enable_short_term) {
917
- w_17 = this.param.w[17];
918
- w_18 = this.param.w[18];
942
+ w_17 = w[17];
943
+ w_18 = w[18];
919
944
  }
920
945
  const next_s_min = s / Math.exp(w_17 * w_18);
921
- new_s = clamp(+next_s_min.toFixed(8), S_MIN, s_after_fail);
922
- }
923
- if (t === 0 && this.param.enable_short_term) {
924
- new_s = s_after_short_term;
946
+ new_s = clamp(roundTo(next_s_min, 8), S_MIN, s_after_fail);
947
+ } else {
948
+ new_s = this.next_recall_stability(d, s, r, g);
925
949
  }
926
950
  const new_d = this.next_difficulty(d, g);
927
951
  return { difficulty: new_d, stability: new_s };
@@ -947,9 +971,7 @@ class BasicScheduler extends AbstractScheduler {
947
971
  const steps_strategy = this.learningStepsStrategy(
948
972
  parameters,
949
973
  card.state,
950
- // In the original learning steps setup (Again = 5m, Hard = 10m, Good = FSRS),
951
- // not adding 1 can cause slight variations in the memory state’s ds.
952
- this.current.state === State.Learning && grade !== Rating.Again && grade !== Rating.Hard ? card.learning_steps + 1 : card.learning_steps
974
+ card.learning_steps
953
975
  );
954
976
  const scheduled_minutes = Math.max(
955
977
  0,
@@ -1006,9 +1028,7 @@ class BasicScheduler extends AbstractScheduler {
1006
1028
  if (exist) {
1007
1029
  return exist;
1008
1030
  }
1009
- const next = TypeConvert.card(this.current);
1010
- next.difficulty = clamp(this.algorithm.init_difficulty(grade), 1, 10);
1011
- next.stability = this.algorithm.init_stability(grade);
1031
+ const next = this.next_ds(this.elapsed_days, grade);
1012
1032
  this.applyLearningSteps(next, grade, State.Learning);
1013
1033
  const item = {
1014
1034
  card: next,
@@ -1022,14 +1042,11 @@ class BasicScheduler extends AbstractScheduler {
1022
1042
  if (exist) {
1023
1043
  return exist;
1024
1044
  }
1025
- const { state, difficulty, stability } = this.last;
1026
- const next = TypeConvert.card(this.current);
1027
- next.difficulty = this.algorithm.next_difficulty(difficulty, grade);
1028
- next.stability = this.algorithm.next_short_term_stability(stability, grade);
1045
+ const next = this.next_ds(this.elapsed_days, grade);
1029
1046
  this.applyLearningSteps(
1030
1047
  next,
1031
1048
  grade,
1032
- state
1049
+ this.last.state
1033
1050
  /** Learning or Relearning */
1034
1051
  );
1035
1052
  const item = {
@@ -1045,21 +1062,14 @@ class BasicScheduler extends AbstractScheduler {
1045
1062
  return exist;
1046
1063
  }
1047
1064
  const interval = this.elapsed_days;
1048
- const { difficulty, stability } = this.last;
1049
- const retrievability = this.algorithm.forgetting_curve(interval, stability);
1050
- const next_again = TypeConvert.card(this.current);
1051
- const next_hard = TypeConvert.card(this.current);
1052
- const next_good = TypeConvert.card(this.current);
1053
- const next_easy = TypeConvert.card(this.current);
1054
- this.next_ds(
1055
- next_again,
1056
- next_hard,
1057
- next_good,
1058
- next_easy,
1059
- difficulty,
1060
- stability,
1061
- retrievability
1065
+ const retrievability = this.algorithm.forgetting_curve(
1066
+ interval,
1067
+ this.current.stability
1062
1068
  );
1069
+ const next_again = this.next_ds(interval, Rating.Again, retrievability);
1070
+ const next_hard = this.next_ds(interval, Rating.Hard, retrievability);
1071
+ const next_good = this.next_ds(interval, Rating.Good, retrievability);
1072
+ const next_easy = this.next_ds(interval, Rating.Easy, retrievability);
1063
1073
  this.next_interval(next_hard, next_good, next_easy, interval);
1064
1074
  this.next_state(next_hard, next_good, next_easy);
1065
1075
  this.applyLearningSteps(next_again, Rating.Again, State.Relearning);
@@ -1089,50 +1099,20 @@ class BasicScheduler extends AbstractScheduler {
1089
1099
  /**
1090
1100
  * Review next_ds
1091
1101
  */
1092
- next_ds(next_again, next_hard, next_good, next_easy, difficulty, stability, retrievability) {
1093
- next_again.difficulty = this.algorithm.next_difficulty(
1094
- difficulty,
1095
- Rating.Again
1096
- );
1097
- const nextSMin = stability / Math.exp(
1098
- this.algorithm.parameters.w[17] * this.algorithm.parameters.w[18]
1099
- );
1100
- const s_after_fail = this.algorithm.next_forget_stability(
1101
- difficulty,
1102
- stability,
1103
- retrievability
1104
- );
1105
- next_again.stability = clamp(+nextSMin.toFixed(8), S_MIN, s_after_fail);
1106
- next_hard.difficulty = this.algorithm.next_difficulty(
1107
- difficulty,
1108
- Rating.Hard
1109
- );
1110
- next_hard.stability = this.algorithm.next_recall_stability(
1111
- difficulty,
1112
- stability,
1113
- retrievability,
1114
- Rating.Hard
1115
- );
1116
- next_good.difficulty = this.algorithm.next_difficulty(
1117
- difficulty,
1118
- Rating.Good
1119
- );
1120
- next_good.stability = this.algorithm.next_recall_stability(
1121
- difficulty,
1122
- stability,
1123
- retrievability,
1124
- Rating.Good
1125
- );
1126
- next_easy.difficulty = this.algorithm.next_difficulty(
1127
- difficulty,
1128
- Rating.Easy
1129
- );
1130
- next_easy.stability = this.algorithm.next_recall_stability(
1131
- difficulty,
1132
- stability,
1133
- retrievability,
1134
- Rating.Easy
1102
+ next_ds(t, g, r) {
1103
+ const next_state = this.algorithm.next_state(
1104
+ {
1105
+ difficulty: this.current.difficulty,
1106
+ stability: this.current.stability
1107
+ },
1108
+ t,
1109
+ g,
1110
+ r
1135
1111
  );
1112
+ const card = TypeConvert.card(this.current);
1113
+ card.difficulty = next_state.difficulty;
1114
+ card.stability = next_state.stability;
1115
+ return card;
1136
1116
  }
1137
1117
  /**
1138
1118
  * Review next_interval
@@ -1175,12 +1155,11 @@ class LongTermScheduler extends AbstractScheduler {
1175
1155
  }
1176
1156
  this.current.scheduled_days = 0;
1177
1157
  this.current.elapsed_days = 0;
1178
- const next_again = TypeConvert.card(this.current);
1179
- const next_hard = TypeConvert.card(this.current);
1180
- const next_good = TypeConvert.card(this.current);
1181
- const next_easy = TypeConvert.card(this.current);
1182
- this.init_ds(next_again, next_hard, next_good, next_easy);
1183
1158
  const first_interval = 0;
1159
+ const next_again = this.next_ds(first_interval, Rating.Again);
1160
+ const next_hard = this.next_ds(first_interval, Rating.Hard);
1161
+ const next_good = this.next_ds(first_interval, Rating.Good);
1162
+ const next_easy = this.next_ds(first_interval, Rating.Easy);
1184
1163
  this.next_interval(
1185
1164
  next_again,
1186
1165
  next_hard,
@@ -1192,31 +1171,20 @@ class LongTermScheduler extends AbstractScheduler {
1192
1171
  this.update_next(next_again, next_hard, next_good, next_easy);
1193
1172
  return this.next.get(grade);
1194
1173
  }
1195
- init_ds(next_again, next_hard, next_good, next_easy) {
1196
- next_again.difficulty = clamp(
1197
- this.algorithm.init_difficulty(Rating.Again),
1198
- 1,
1199
- 10
1200
- );
1201
- next_again.stability = this.algorithm.init_stability(Rating.Again);
1202
- next_hard.difficulty = clamp(
1203
- this.algorithm.init_difficulty(Rating.Hard),
1204
- 1,
1205
- 10
1206
- );
1207
- next_hard.stability = this.algorithm.init_stability(Rating.Hard);
1208
- next_good.difficulty = clamp(
1209
- this.algorithm.init_difficulty(Rating.Good),
1210
- 1,
1211
- 10
1212
- );
1213
- next_good.stability = this.algorithm.init_stability(Rating.Good);
1214
- next_easy.difficulty = clamp(
1215
- this.algorithm.init_difficulty(Rating.Easy),
1216
- 1,
1217
- 10
1174
+ next_ds(t, g, r) {
1175
+ const next_state = this.algorithm.next_state(
1176
+ {
1177
+ difficulty: this.current.difficulty,
1178
+ stability: this.current.stability
1179
+ },
1180
+ t,
1181
+ g,
1182
+ r
1218
1183
  );
1219
- next_easy.stability = this.algorithm.init_stability(Rating.Easy);
1184
+ const card = TypeConvert.card(this.current);
1185
+ card.difficulty = next_state.difficulty;
1186
+ card.stability = next_state.stability;
1187
+ return card;
1220
1188
  }
1221
1189
  /**
1222
1190
  * @see https://github.com/open-spaced-repetition/ts-fsrs/issues/98#issuecomment-2241923194
@@ -1230,72 +1198,20 @@ class LongTermScheduler extends AbstractScheduler {
1230
1198
  return exist;
1231
1199
  }
1232
1200
  const interval = this.elapsed_days;
1233
- const { difficulty, stability } = this.last;
1234
- const retrievability = this.algorithm.forgetting_curve(interval, stability);
1235
- const next_again = TypeConvert.card(this.current);
1236
- const next_hard = TypeConvert.card(this.current);
1237
- const next_good = TypeConvert.card(this.current);
1238
- const next_easy = TypeConvert.card(this.current);
1239
- this.next_ds(
1240
- next_again,
1241
- next_hard,
1242
- next_good,
1243
- next_easy,
1244
- difficulty,
1245
- stability,
1246
- retrievability
1201
+ const retrievability = this.algorithm.forgetting_curve(
1202
+ interval,
1203
+ this.current.stability
1247
1204
  );
1205
+ const next_again = this.next_ds(interval, Rating.Again, retrievability);
1206
+ const next_hard = this.next_ds(interval, Rating.Hard, retrievability);
1207
+ const next_good = this.next_ds(interval, Rating.Good, retrievability);
1208
+ const next_easy = this.next_ds(interval, Rating.Easy, retrievability);
1248
1209
  this.next_interval(next_again, next_hard, next_good, next_easy, interval);
1249
1210
  this.next_state(next_again, next_hard, next_good, next_easy);
1250
1211
  next_again.lapses += 1;
1251
1212
  this.update_next(next_again, next_hard, next_good, next_easy);
1252
1213
  return this.next.get(grade);
1253
1214
  }
1254
- /**
1255
- * Review next_ds
1256
- */
1257
- next_ds(next_again, next_hard, next_good, next_easy, difficulty, stability, retrievability) {
1258
- next_again.difficulty = this.algorithm.next_difficulty(
1259
- difficulty,
1260
- Rating.Again
1261
- );
1262
- const s_after_fail = this.algorithm.next_forget_stability(
1263
- difficulty,
1264
- stability,
1265
- retrievability
1266
- );
1267
- next_again.stability = clamp(stability, S_MIN, s_after_fail);
1268
- next_hard.difficulty = this.algorithm.next_difficulty(
1269
- difficulty,
1270
- Rating.Hard
1271
- );
1272
- next_hard.stability = this.algorithm.next_recall_stability(
1273
- difficulty,
1274
- stability,
1275
- retrievability,
1276
- Rating.Hard
1277
- );
1278
- next_good.difficulty = this.algorithm.next_difficulty(
1279
- difficulty,
1280
- Rating.Good
1281
- );
1282
- next_good.stability = this.algorithm.next_recall_stability(
1283
- difficulty,
1284
- stability,
1285
- retrievability,
1286
- Rating.Good
1287
- );
1288
- next_easy.difficulty = this.algorithm.next_difficulty(
1289
- difficulty,
1290
- Rating.Easy
1291
- );
1292
- next_easy.stability = this.algorithm.next_recall_stability(
1293
- difficulty,
1294
- stability,
1295
- retrievability,
1296
- Rating.Easy
1297
- );
1298
- }
1299
1215
  /**
1300
1216
  * Review/New next_interval
1301
1217
  */
@@ -1983,6 +1899,7 @@ exports.fsrs = fsrs;
1983
1899
  exports.generatorParameters = generatorParameters;
1984
1900
  exports.get_fuzz_range = get_fuzz_range;
1985
1901
  exports.migrateParameters = migrateParameters;
1902
+ exports.roundTo = roundTo;
1986
1903
  exports.show_diff_message = show_diff_message;
1987
1904
  module.exports = Object.assign(exports.default || {}, exports)
1988
1905
  //# sourceMappingURL=index.cjs.map