diff --git a/HISTORY.rst b/HISTORY.rst index be57a5b..fe16706 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,6 +1,12 @@ ======= History ======= +2025.9.22: Bugfix: fixed floating-point precision issues in For loops + * This fix addresses floating-point precision issues in For loops that caused early + termination and generated malformed directory names (e.g., "3.00000000002" or + "2.999999999998"). The fix calculates the number of decimal places from loop + parameters and applies rounding to maintain precision throughout loop iterations. + 2025.7.23: Bugfix: options not properly set if the loop is in a subflowchart * The options of the steps in the loop were not set correctly if the loop was in a subflowchart in e.g. a substep step or training step. diff --git a/loop_step/loop.py b/loop_step/loop.py index 018be31..29164bd 100644 --- a/loop_step/loop.py +++ b/loop_step/loop.py @@ -25,6 +25,16 @@ printer = printing.getPrinter("loop") +def _get_decimal_places(value): + """Get the number of decimal places in a float.""" + if not isinstance(value, float): + return 0 + parts = str(value).split(".") + if len(parts) == 2: + return len(parts[1]) + return 0 + + class BreakLoop(Exception): """Indicates that SEAMM should break from the loop""" @@ -217,30 +227,33 @@ def run(self): # Some local variables need each iteration # See if loop variables are all integers - integers = True start = P["start"] if isinstance(start, str): start = float(start) if start.is_integer(): start = int(start) - else: - integers = False + step = P["step"] if isinstance(step, str): step = float(step) if step.is_integer(): step = int(step) - else: - integers = False + end = P["end"] if isinstance(end, str): end = float(end) if end.is_integer(): end = int(end) - else: - integers = False - if integers: + # If floating point, remove nasty extra digits + ndigits = max( + _get_decimal_places(start), + _get_decimal_places(step), + _get_decimal_places(end), + ) + if ndigits > 0: + fmt = f".{ndigits}f" + else: fmt = f"0{len(str(end))}d" if self._loop_value is None: @@ -261,6 +274,8 @@ def run(self): while tmp <= end: count += 1 tmp += step + if ndigits > 0: + tmp = round(tmp, ndigits) self._loop_length = count printer.important( __( @@ -520,12 +535,13 @@ def run(self): self._loop_count += 1 if self._loop_count > 1: self._loop_value += step + if ndigits > 0: + self._loop_value = round(self._loop_value, ndigits) self.set_variable(P["variable"], self._loop_value) - # For integer loops, we can use the value for the directory names - if integers: - self._custom_directory_name = f"iter_{self._loop_value:{fmt}}" + # Use the value for the directory names + self._custom_directory_name = f"iter_{self._loop_value:{fmt}}" # Set up the index variables tmp = self.get_variable("_loop_indices")