Skip to content

Commit aeb90db

Browse files
authored
Add an error margin when comparing floats. (elastic#129721) (elastic#129870)
We add a margin of error when comparing floats to the DynamicFieldMatcher to account for a small loss of accuracy when loading fields from synthetic source. (cherry picked from commit d859366) # Conflicts: # muted-tests.yml
1 parent 6cf7526 commit aeb90db

File tree

1 file changed

+37
-17
lines changed

1 file changed

+37
-17
lines changed

test/framework/src/main/java/org/elasticsearch/datageneration/matchers/source/DynamicFieldMatcher.java

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@
1717
import java.util.List;
1818
import java.util.Objects;
1919
import java.util.Optional;
20-
import java.util.Set;
2120
import java.util.function.Function;
22-
import java.util.stream.Collectors;
21+
import java.util.function.Supplier;
2322

2423
import static org.elasticsearch.datageneration.matchers.Messages.formatErrorMessage;
2524
import static org.elasticsearch.datageneration.matchers.Messages.prettyPrintCollections;
2625

2726
class DynamicFieldMatcher {
27+
private static final double FLOAT_ERROR_MARGIN = 1e-8;
2828
private final XContentBuilder actualMappings;
2929
private final Settings.Builder actualSettings;
3030
private final XContentBuilder expectedMappings;
@@ -62,31 +62,51 @@ public MatchResult match(List<Object> actual, List<Object> expected) {
6262

6363
var normalizedActual = normalizeDoubles(actual);
6464
var normalizedExpected = normalizeDoubles(expected);
65+
Supplier<MatchResult> noMatchSupplier = () -> MatchResult.noMatch(
66+
formatErrorMessage(
67+
actualMappings,
68+
actualSettings,
69+
expectedMappings,
70+
expectedSettings,
71+
"Values of dynamically mapped field containing double values don't match after normalization, normalized "
72+
+ prettyPrintCollections(normalizedActual, normalizedExpected)
73+
)
74+
);
6575

66-
return normalizedActual.equals(normalizedExpected)
67-
? MatchResult.match()
68-
: MatchResult.noMatch(
69-
formatErrorMessage(
70-
actualMappings,
71-
actualSettings,
72-
expectedMappings,
73-
expectedSettings,
74-
"Values of dynamically mapped field containing double values don't match after normalization, normalized "
75-
+ prettyPrintCollections(normalizedActual, normalizedExpected)
76-
)
77-
);
76+
if (normalizedActual.size() != normalizedExpected.size()) {
77+
return noMatchSupplier.get();
78+
}
79+
80+
for (int i = 0; i < normalizedActual.size(); i++) {
81+
if (floatEquals(normalizedActual.get(i), normalizedExpected.get(i)) == false) {
82+
return noMatchSupplier.get();
83+
}
84+
}
85+
86+
return MatchResult.match();
7887
}
7988

8089
return matchWithGenericMatcher(actual, expected);
8190
}
8291

83-
private static Set<Float> normalizeDoubles(List<Object> values) {
92+
/**
93+
* We make the normalisation of double values stricter than {@link SourceTransforms#normalizeValues} to facilitate the equality of the
94+
* values within a margin of error. Synthetic source does support duplicate values and preserves the order, but it loses some accuracy,
95+
* this is why the margin of error is very important. In the future, we can make {@link SourceTransforms#normalizeValues} also stricter.
96+
*/
97+
private static List<Float> normalizeDoubles(List<Object> values) {
8498
if (values == null) {
85-
return Set.of();
99+
return List.of();
86100
}
87101

88102
Function<Object, Float> toFloat = (o) -> o instanceof Number n ? n.floatValue() : Float.parseFloat((String) o);
89-
return values.stream().filter(Objects::nonNull).map(toFloat).collect(Collectors.toSet());
103+
104+
// We skip nulls because they trip the pretty print collections.
105+
return values.stream().filter(Objects::nonNull).map(toFloat).toList();
106+
}
107+
108+
private static boolean floatEquals(Float actual, Float expected) {
109+
return Math.abs(actual - expected) < FLOAT_ERROR_MARGIN;
90110
}
91111

92112
private MatchResult matchWithGenericMatcher(List<Object> actualValues, List<Object> expectedValues) {

0 commit comments

Comments
 (0)