Index: cpp/frams/genetics/genooperators.cpp
===================================================================
--- cpp/frams/genetics/genooperators.cpp	(revision 1249)
+++ cpp/frams/genetics/genooperators.cpp	(revision 1254)
@@ -5,4 +5,5 @@
 #include <ctype.h>  //isupper()
 #include <algorithm> // std::min, std::max
+#include <cmath> // std::floor()
 #include "genooperators.h"
 #include <common/log.h>
@@ -203,5 +204,5 @@
 		if (result > mx) result = mx - (result - mx); else
 			if (result < mn) result = mn + (mn - result);
-		//wrap (just in case 'result' exceeded the allowed range so much that after reflection above it exceeded the other boundary):
+		//wrap (just in case 'result' exceeded the allowed range so much that after the reflection above it exceeded the other boundary):
 		if (result > mx) result = mn + fmod(result - mx, mx - mn); else
 			if (result < mn) result = mn + fmod(mn - result, mx - mn);
@@ -210,7 +211,8 @@
 			//reflect and wrap above may have changed the (limited) precision, so try to round again (maybe unnecessarily, because we don't know if reflect+wrap above were triggered)
 			double result_try = round(result, 3);
-			if (mn <= result_try && result_try <= mx) result = result_try; //after rounding still witin allowed range, so keep rounded value
-		}
-	}
+			if (mn <= result_try && result_try <= mx) result = result_try; //after rounding still within allowed range, so keep rounded value
+		}
+	}
+	clipNegativeZeroIfNeeded(result, mn); //so we don't get -0.0 when minimum is 0.0
 	return result;
 }
@@ -222,7 +224,14 @@
 }
 
-void GenoOperators::setIntFromDoubleWithProbabilisticDithering(ParamInterface &p, int index, double value) //TODO
-{
-	p.setInt(index, (paInt)(value + 0.5)); //TODO value=2.499 will result in 2 and 2.5 will result in 3, but we want these cases to be 2 or 3 with almost equal probability. value=2.1 should be mostly 2, rarely 3. Careful with negative values (test it!)
+void GenoOperators::setIntFromDoubleWithProbabilisticDithering(ParamInterface &p, int index, double value)
+{
+	// Deterministic rounding to the closest integer:
+	//value += 0.5; // value==2.499 will become int 2 and value==2.5 will become int 3, but we want these cases to be 2 or 3 with almost equal probability (stochastic rounding).
+
+	//stochastic rounding (value==2.1 should turn in most cases to int 2, rarely to int 3; value==-2.1 should become mostly int -2, rarely int -3):
+	double lower = std::floor(value);
+	value = rndDouble(1) < (value - lower) ? lower + 1 : lower;
+
+	p.setInt(index, (paInt)value);
 }
 
Index: cpp/frams/genetics/genooperators.h
===================================================================
--- cpp/frams/genetics/genooperators.h	(revision 1249)
+++ cpp/frams/genetics/genooperators.h	(revision 1254)
@@ -195,5 +195,5 @@
 	static double mutateCreep(char type, double current, double mn, double mx, double stddev, bool limit_precision_3digits); ///<just as mutateCreepNoLimit(), but forces mutated value into the [mn,mx] range using the 'reflect' approach.
 	static double mutateCreep(char type, double current, double mn, double mx, bool limit_precision_3digits); ///<just as mutateCreepNoLimit(), but forces mutated value into the [\a mn,\a mx] range using the 'reflect' approach and assumes standard deviation to be a fraction of the mx-mn interval width.
-	static void setIntFromDoubleWithProbabilisticDithering(ParamInterface &p, int index, double value); ///<sets a double value in an integer field; when a value is non-integer, applies random "dithering" so that both lower and higher integer value have some chance to be set.
+	static void setIntFromDoubleWithProbabilisticDithering(ParamInterface &p, int index, double value); ///<sets a double value in an integer field; when a value is non-integer, applies stochastic rounding (random "dithering") so that both lower and higher integer value have some chance to be set.
 	static void linearMix(vector<double> &p1, vector<double> &p2, double proportion); ///<mixes two real-valued vectors; inherited proportion should be within [0,1]; 1.0 does not change values (all inherited), 0.5 causes both vectors to become their average, 0.0 swaps values (none inherited).
 	static void linearMix(ParamInterface &p1, int i1, ParamInterface &p2, int i2, double proportion); ///<mixes i1'th and i2'th properties of p1 and p2; inherited proportion should be within [0,1]; 1.0 does not change values (all inherited), 0.5 causes both properties to become their average, 0.0 swaps values (none inherited). For integer properties applies random "dithering" when necessary.
@@ -221,5 +221,5 @@
 	static string simplifiedModifiersFixedOrder(const char *str_of_char_pairs, vector<int> &char_counts); ///<returns a sequence of chars from \a str_of_char_pairs based on how many times each char occurred in \a char_counts. Assume that an even-index char and the following odd-index char have the opposite influence, so they cancel out. We don't use this function, because a fixed order imposed by this function means that the number of different parameter values produced by a sequence of modifiers is lowered (N same-letter upper- and lower-case chars yield only 2*N different values). Due to how modifiers work, the effect of aaA, aAa, Aaa etc. is different (N same-letter upper- and lower-case chars yield 2^N different values), so simplifying modifiers should not impose any order, should not interfere with their original order, and should not cancel out antagonistic modifiers - see \a simplifiedModifiers() and geneprops_test.cpp.
 	//@}
-	static string simplifiedModifiers(const string &original); ///<from the \a original sequence removes modifiers that are too numerous (exceeding a defined threshold number), starting the removal from the leftmost (="oldest" when interpreting the sequence from left to right) ones.
+	static string simplifiedModifiers(const string &original); ///<from the \a original sequence removes modifiers that are too numerous (exceeding a defined threshold number), starting the removal from the least-significant, leftmost (="oldest" when interpreting the sequence from left to right) ones. Contrary to \a simplifiedModifiersFixedOrder(), this kind of simplification preserves 2^N different sequences for each upper/lower-case modifier and thus 2^N different values of a given property (see geneprops.cpp), but the values resulting from these sequences constitute a landscape not as easy for optimization as in the case of 2*N, where the effect of each mutation could be independent and additive (no epistasis). So for a given sequence length, the 2^N case allows for a higher resolution at the cost of a more rugged fitness landscape than the 2*N case.
 };
 
