diff --git a/src/app/components/generic-calculator/calculator.component.ts b/src/app/components/generic-calculator/calculator.component.ts
index 45d11200ab78f9b1713af8a79eb8fa1fe62e2f10..28ed8e3363c24634cdbfd0c14e44523d6af9aa7d 100644
--- a/src/app/components/generic-calculator/calculator.component.ts
+++ b/src/app/components/generic-calculator/calculator.component.ts
@@ -14,10 +14,10 @@ import {
     acSection,
     ParamDefinition,
     round,
-    VariatedDetails
+    Nub
 } from "jalhyd";
 
-import { longestVarParam } from "../../util";
+import { generateValuesCombination } from "../../util";
 
 import { AppComponent } from "../../app.component";
 import { FormulaireService } from "../../services/formulaire.service";
@@ -714,115 +714,33 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe
     }
 
     /**
-     * Génère une liste de valeurs de tirant d'eau pour un couple Z1/ZF1 ou Z2/ZF2 donné, que ces
-     * paramètres soient fixés, variés et/ou calculés
-     * @TODO factoriser avec generateIfValuesForSP() la partie "générer des combinaisons de valeurs depuis N paramètres"
+     * Génère une liste de valeurs de tirant d'eau pour un couple Z1/ZF1 ou Z2/ZF2 donné
      * @param Z cote de leau (Z1 ou Z2)
      * @param ZF cote de fond (ZF1 ou ZF2)
      */
     private generateYValuesForSP(Z: ParamDefinition, ZF: ParamDefinition): number | number[] {
         const bief = (this._formulaire.currentNub as Bief);
-        if (
-            (Z.isCalculated && bief.resultHasMultipleValues())
-            || Z.hasMultipleValues
-            || ZF.hasMultipleValues
-        ) {
-            // find longest values list with standard Nub method
-            const { size } = longestVarParam(bief.findVariatedParams());
-
-            // extend values list for Z / Z1 if they vary
-            let ZVals: number[];
-            if (Z.isCalculated) {
-                ZVals = bief.result.getCalculatedValues();
-            } else {
-                if (Z.hasMultipleValues) {
-                    ZVals = Z.getInferredValuesList(size);
-                } else {
-                    ZVals = [ Z.singleValue ];
-                }
-            }
-            let ZFVals: number[];
-            if (ZF.hasMultipleValues) {
-                ZFVals = ZF.getInferredValuesList(size);
-            } else {
-                ZFVals = [ ZF.singleValue ];
+        return generateValuesCombination(
+            bief,
+            [ Z, ZF ],
+            (nub: Nub, values: { [key: string]: number }): number => {
+                return round(values[Z.symbol] - values[ZF.symbol], 3);
             }
-
-            // calculate Y values
-            const Ys: number[] = [];
-            for (let i = 0; i < size; i++) {
-                // using "%" because one of the two lists might have only 1 value
-                Ys.push(round(ZVals[i % ZVals.length] - ZFVals[i % ZFVals.length], 3));
-            }
-            return Ys;
-
-        } else {
-            // .V returns calculated value or latest .v; no problem since no parameter is varying
-            // @TODO check that .v is not polluted by any DICHO calc ?
-            const Y = round(Z.V - ZF.V, 3);
-            return Y;
-        }
+        );
     }
 
     /**
-     * Génère une liste de valeurs de pente en fonction de ZF1, ZF2 et Long,
-     * que ces paramètres soient fixés, variés et/ou calculés
+     * Génère une liste de valeurs de pente en fonction de ZF1, ZF2 et Long
      */
     private generateIfValuesForSP(): number | number[] {
         const bief = (this._formulaire.currentNub as Bief);
-        const ZF1 = bief.prms.ZF1;
-        const ZF2 = bief.prms.ZF2;
-        const Long = bief.prms.Long;
-        if (ZF1.hasMultipleValues || ZF2.hasMultipleValues || Long.hasMultipleValues) {
-            // find longest values list
-            const variated: VariatedDetails[] = [
-                {
-                    param: ZF1,
-                    values: ZF1.paramValues
-                },
-                {
-                    param: ZF2,
-                    values: ZF2.paramValues
-                },
-                {
-                    param: Long,
-                    values: Long.paramValues
-                }
-            ];
-            const { size } = longestVarParam(variated);
-
-            // extend values list for ZF1 / ZF2 / Long if they vary
-            let ZF1Vals: number[];
-            if (ZF1.hasMultipleValues) {
-                ZF1Vals = ZF1.getInferredValuesList(size);
-            } else {
-                ZF1Vals = [ ZF1.singleValue ];
-            }
-            let ZF2Vals: number[];
-            if (ZF2.hasMultipleValues) {
-                ZF2Vals = ZF2.getInferredValuesList(size);
-            } else {
-                ZF2Vals = [ ZF2.singleValue ];
-            }
-            let LongVals: number[];
-            if (Long.hasMultipleValues) {
-                LongVals = Long.getInferredValuesList(size);
-            } else {
-                LongVals = [ Long.singleValue ];
+        return generateValuesCombination(
+            bief,
+            [ bief.prms.ZF1, bief.prms.ZF2, bief.prms.Long ],
+            (nub: Nub, values: { [key: string]: number }): number => {
+                return round((values["ZF1"] - values["ZF2"]) / values["Long"], 5);
             }
-
-            // calculate If values
-            const Ifs: number[] = [];
-            for (let i = 0; i < size; i++) {
-                // using "%" because one of the three lists might have only 1 value
-                Ifs.push(round((ZF1Vals[i % ZF1Vals.length] - ZF2Vals[i % ZF2Vals.length]) / LongVals[i % LongVals.length], 5));
-            }
-            return Ifs;
-
-        } else {
-            const If = round((ZF1.singleValue - ZF2.singleValue) / Long.singleValue, 5);
-            return If;
-        }
+        );
     }
 
     /**
diff --git a/src/app/util.ts b/src/app/util.ts
index 5339bad8dbe662967ecbb775c7a118a833644360..6f5719bc5b0888c222f55633d3a6171b17a90e81 100644
--- a/src/app/util.ts
+++ b/src/app/util.ts
@@ -85,3 +85,79 @@ export function longestVarParam(variated: VariatedDetails[]): { param: ParamDefi
         size: realSize
     };
 }
+
+
+/**
+ * Generates a combination of values from a list of parameters, by extending the values
+ * list of each parameter if needed, then applying a formula to every values n-uple
+ *
+ * @param nub the Nub holding the parameters
+ * @param params the parameters to combine
+ * @param formula the formula to apply, receives 2 args: the Nub, and a map of current
+ *      values for each combined parameter symbol
+ * @param variatedParams an optional list of parameters used to determine maximum length
+ *      of combined values list; if undefined, all params of the Nub will be used
+ */
+export function generateValuesCombination(
+    nub: Nub,
+    params: ParamDefinition[],
+    formula: (nub: Nub, values: { [key: string]: number }) => number,
+    variatedParams?: ParamDefinition[]
+): number | number[] {
+
+    let variates = false;
+    for (const p of params) {
+        variates = variates || p.hasMultipleValues; // manages CALC mode too
+    }
+    if (variates) {
+        let size: number;
+        // find longest values list with standard Nub method
+        if (variatedParams !== undefined) {
+            const variated: VariatedDetails[] = [];
+            for (const vp of variatedParams) {
+                variated.push({
+                    param: vp,
+                    values: vp.paramValues
+                });
+            }
+            size = longestVarParam(variated).size;
+        } else {
+            size = longestVarParam(nub.findVariatedParams()).size;
+        }
+
+        // extend values list for all params
+        const values: { [key: string]: number[] } = {};
+        for (const p of params) {
+            if (p.isCalculated) {
+                values[p.symbol] = nub.result.getCalculatedValues();
+            } else {
+                if (p.hasMultipleValues) {
+                    values[p.symbol] = p.getInferredValuesList(size);
+                } else {
+                    values[p.symbol] = [ p.singleValue ];
+                }
+            }
+        }
+
+        // calculate Y values
+        const Vs: number[] = [];
+        for (let i = 0; i < size; i++) {
+            const vals: { [key: string]: number } = {};
+            for (const p of params) {
+                // using "%" because lists might have only 1 value
+                vals[p.symbol] = values[p.symbol][i % values[p.symbol].length];
+            }
+            Vs.push(formula(nub, vals));
+        }
+        // variated result
+        return Vs;
+
+    } else {
+        const vals: { [key: string]: number } = {};
+        for (const p of params) {
+            vals[p.symbol] = p.V;
+        }
+        // single result
+        return formula(nub, vals);
+    }
+}