diff --git a/.github/scripts/generate-quality-report.py b/.github/scripts/generate-quality-report.py index 51bf1b0328..bd9cbc4b91 100755 --- a/.github/scripts/generate-quality-report.py +++ b/.github/scripts/generate-quality-report.py @@ -766,7 +766,8 @@ def main() -> None: "EQ_CHECK_FOR_OPERAND_NOT_COMPATIBLE_WITH_THIS", "IA_AMBIGUOUS_INVOCATION_OF_INHERITED_OR_OUTER_METHOD", "RpC_REPEATED_CONDITIONAL_TEST", - "ES_COMPARING_PARAMETER_STRING_WITH_EQ" + "ES_COMPARING_PARAMETER_STRING_WITH_EQ", + "FE_FLOATING_POINT_EQUALITY" } violations = [ f for f in spotbugs.findings diff --git a/CodenameOne/src/com/codename1/charts/models/XYSeries.java b/CodenameOne/src/com/codename1/charts/models/XYSeries.java index b3be28a917..7aa03f00a8 100644 --- a/CodenameOne/src/com/codename1/charts/models/XYSeries.java +++ b/CodenameOne/src/com/codename1/charts/models/XYSeries.java @@ -188,7 +188,7 @@ public void remove(int index) { XYEntry removedEntry = mXY.removeByIndex(index); double removedX = removedEntry.getKey(); double removedY = removedEntry.getValue(); - if (removedX == mMinX || removedX == mMaxX || removedY == mMinY || removedY == mMaxY) { + if (com.codename1.util.MathUtil.compare(removedX, mMinX) == 0 || com.codename1.util.MathUtil.compare(removedX, mMaxX) == 0 || com.codename1.util.MathUtil.compare(removedY, mMinY) == 0 || com.codename1.util.MathUtil.compare(removedY, mMaxY) == 0) { initRange(); } } diff --git a/CodenameOne/src/com/codename1/charts/models/XYValueSeries.java b/CodenameOne/src/com/codename1/charts/models/XYValueSeries.java index 70f8d2e857..5b7e80e708 100644 --- a/CodenameOne/src/com/codename1/charts/models/XYValueSeries.java +++ b/CodenameOne/src/com/codename1/charts/models/XYValueSeries.java @@ -94,7 +94,7 @@ public void add(double x, double y) { public void remove(int index) { super.remove(index); double removedValue = mValue.remove(index); - if (removedValue == mMinValue || removedValue == mMaxValue) { + if (com.codename1.util.MathUtil.compare(removedValue, mMinValue) == 0 || com.codename1.util.MathUtil.compare(removedValue, mMaxValue) == 0) { initRange(); } } diff --git a/CodenameOne/src/com/codename1/location/Location.java b/CodenameOne/src/com/codename1/location/Location.java index 4643a6c002..e54037b6be 100644 --- a/CodenameOne/src/com/codename1/location/Location.java +++ b/CodenameOne/src/com/codename1/location/Location.java @@ -294,7 +294,7 @@ public int compare(Location o1, Location o2) { */ boolean equalsLatLng(Location l) { - return l != null && l.latitude == latitude && l.longitude == longitude; + return l != null && com.codename1.util.MathUtil.compare(l.latitude, latitude) == 0 && com.codename1.util.MathUtil.compare(l.longitude, longitude) == 0; } } diff --git a/CodenameOne/src/com/codename1/ui/Command.java b/CodenameOne/src/com/codename1/ui/Command.java index ab87e4b06d..0b9d804475 100644 --- a/CodenameOne/src/com/codename1/ui/Command.java +++ b/CodenameOne/src/com/codename1/ui/Command.java @@ -275,12 +275,12 @@ public boolean equals(Object obj) { if (((Command) obj).command == null) { return obj.getClass() == getClass() && command == null && ((Command) obj).icon == icon && ((Command) obj).commandId == commandId && - ((Command) obj).materialIcon == materialIcon && ((Command) obj).materialIconSize == materialIconSize && + ((Command) obj).materialIcon == materialIcon && com.codename1.util.MathUtil.compare(((Command) obj).materialIconSize, materialIconSize) == 0 && (Objects.equals(clientProperties, ((Command) obj).clientProperties)); } else { return obj.getClass() == getClass() && ((Command) obj).command.equals(command) && ((Command) obj).icon == icon && ((Command) obj).commandId == commandId && - ((Command) obj).materialIcon == materialIcon && ((Command) obj).materialIconSize == materialIconSize && + ((Command) obj).materialIcon == materialIcon && com.codename1.util.MathUtil.compare(((Command) obj).materialIconSize, materialIconSize) == 0 && (Objects.equals(clientProperties, ((Command) obj).clientProperties)); } } diff --git a/CodenameOne/src/com/codename1/ui/Stroke.java b/CodenameOne/src/com/codename1/ui/Stroke.java index cdbd208a5b..16498c93f7 100644 --- a/CodenameOne/src/com/codename1/ui/Stroke.java +++ b/CodenameOne/src/com/codename1/ui/Stroke.java @@ -227,7 +227,7 @@ public void setMiterLimit(float miterLimit) { public boolean equals(Object obj) { if (obj instanceof Stroke) { Stroke s = (Stroke) obj; - return (s.miterLimit == miterLimit && s.capStyle == capStyle && s.joinStyle == joinStyle && s.lineWidth == lineWidth); + return (com.codename1.util.MathUtil.compare(s.miterLimit, miterLimit) == 0 && s.capStyle == capStyle && s.joinStyle == joinStyle && com.codename1.util.MathUtil.compare(s.lineWidth, lineWidth) == 0); } return false; } diff --git a/CodenameOne/src/com/codename1/ui/geom/GeneralPath.java b/CodenameOne/src/com/codename1/ui/geom/GeneralPath.java index 7d18c036ba..ac2f6f9a52 100644 --- a/CodenameOne/src/com/codename1/ui/geom/GeneralPath.java +++ b/CodenameOne/src/com/codename1/ui/geom/GeneralPath.java @@ -2224,7 +2224,7 @@ private static float[] intersectLineWithRectAsHash(float x1, float y1, float x2, float minX = Math.min(x1, x2); float maxX = Math.max(x1, x2); int i = 0; - if (dx == 0) { + if (isZero(dx)) { if (ry1 > minY && ry1 < maxY) { num++; x[i++] = ry1; @@ -2255,7 +2255,7 @@ private static float[] intersectLineWithRectAsHash(float x1, float y1, float x2, out[8] = num; - } else if (dy == 0) { + } else if (isZero(dy)) { if (rx1 > minX && rx1 < maxX) { num++; x[i++] = rx1; @@ -2344,8 +2344,8 @@ public static int solveQuad(double[] eqn, double[] res) { double b = eqn[1]; double c = eqn[0]; int rc = 0; - if (a == 0.0) { - if (b == 0.0) { + if (isZero(a)) { + if (isZero(b)) { return -1; } res[rc++] = -c / b; @@ -2358,7 +2358,7 @@ public static int solveQuad(double[] eqn, double[] res) { d = Math.sqrt(d); res[rc++] = (-b + d) / (a * 2.0); // d != 0.0 - if (d != 0.0) { + if (!isZero(d)) { res[rc++] = (-b - d) / (a * 2.0); } } @@ -2374,7 +2374,7 @@ public static int solveQuad(double[] eqn, double[] res) { */ public static int solveCubic(double[] eqn, double[] res) { double d = eqn[3]; - if (d == 0) { + if (isZero(d)) { return solveQuad(eqn, res); } double a = eqn[2] / d; @@ -2453,7 +2453,7 @@ public static int crossLine(double x1, double y1, double x2, double y2, double x if ((x < x1 && x < x2) || (x > x1 && x > x2) || (y > y1 && y > y2) || - (x1 == x2)) { + (isZero(x1 - x2))) { return 0; } @@ -3025,14 +3025,14 @@ int cross(double[] res, int rc, double py1, double py2) { } // CURVE-START if (t < DELTA) { - if (py1 < 0.0 && (bx != 0.0 ? bx : ax - bx) < 0.0) { + if (py1 < 0.0 && (!isZero(bx) ? bx : ax - bx) < 0.0) { cross--; } continue; } // CURVE-END if (t > 1 - DELTA) { - if (py1 < ay && (ax != bx ? ax - bx : bx) > 0.0) { + if (py1 < ay && (!isZero(ax - bx) ? ax - bx : bx) > 0.0) { cross++; } continue; @@ -3060,10 +3060,10 @@ int solvePoint(double[] res, double px) { int solveExtrem(double[] res) { int rc = 0; - if (Ax != 0.0) { + if (!isZero(Ax)) { res[rc++] = -Bx / (Ax + Ax); } - if (Ay != 0.0) { + if (!isZero(Ay)) { res[rc++] = -By / (Ay + Ay); } return rc; @@ -3130,14 +3130,14 @@ int cross(double[] res, int rc, double py1, double py2) { } // CURVE-START if (t < DELTA) { - if (py1 < 0.0 && (bx != 0.0 ? bx : (cx != bx ? cx - bx : ax - cx)) < 0.0) { + if (py1 < 0.0 && (!isZero(bx) ? bx : (!isZero(cx - bx) ? cx - bx : ax - cx)) < 0.0) { cross--; } continue; } // CURVE-END if (t > 1 - DELTA) { - if (py1 < ay && (ax != cx ? ax - cx : (cx != bx ? cx - bx : bx)) > 0.0) { + if (py1 < ay && (!isZero(ax - cx) ? ax - cx : (!isZero(cx - bx) ? cx - bx : bx)) > 0.0) { cross++; } continue; diff --git a/CodenameOne/src/com/codename1/ui/layouts/LayeredLayout.java b/CodenameOne/src/com/codename1/ui/layouts/LayeredLayout.java index fd28527acf..64c4e6aa28 100644 --- a/CodenameOne/src/com/codename1/ui/layouts/LayeredLayout.java +++ b/CodenameOne/src/com/codename1/ui/layouts/LayeredLayout.java @@ -3256,7 +3256,7 @@ public Inset changeReference(Container parent, Component newRef, float pos) { // // This could potentially affect the opposite inset if it is a percentage // referenceComponent(newRef).referencePosition(pos); //} else { - if (newRef != referenceComponent || pos != referencePosition) { + if (newRef != referenceComponent || com.codename1.util.MathUtil.compare(pos, referencePosition) != 0) { // This may potentially affect both this inset // and the opposite inset if it is either flexible or // percent. @@ -3437,7 +3437,7 @@ public Inset translatePixels(int delta, boolean preferMM, Container parent) { return this; } float percentDelta = delta / relH * 100f; - if (percentDelta == Float.NEGATIVE_INFINITY || percentDelta == Float.POSITIVE_INFINITY) { + if (com.codename1.util.MathUtil.compare(percentDelta, Float.NEGATIVE_INFINITY) == 0 || com.codename1.util.MathUtil.compare(percentDelta, Float.POSITIVE_INFINITY) == 0) { percentDelta = 0f; } value += percentDelta; @@ -3450,7 +3450,7 @@ public Inset translatePixels(int delta, boolean preferMM, Container parent) { } float percentDelta = delta / relH * 100f; //System.out.println("percentDelta="+percentDelta); - if (percentDelta == Float.NEGATIVE_INFINITY || percentDelta == Float.POSITIVE_INFINITY) { + if (com.codename1.util.MathUtil.compare(percentDelta, Float.NEGATIVE_INFINITY) == 0 || com.codename1.util.MathUtil.compare(percentDelta, Float.POSITIVE_INFINITY) == 0) { percentDelta = 0f; } value += percentDelta; diff --git a/CodenameOne/src/com/codename1/ui/plaf/Border.java b/CodenameOne/src/com/codename1/ui/plaf/Border.java index cc95167e36..cda5c3502a 100644 --- a/CodenameOne/src/com/codename1/ui/plaf/Border.java +++ b/CodenameOne/src/com/codename1/ui/plaf/Border.java @@ -1102,7 +1102,7 @@ public boolean equals(Object obj) { boolean v = ((themeColors == b.themeColors) && (type == b.type) && - (thickness == b.thickness) && + (com.codename1.util.MathUtil.compare(thickness, b.thickness) == 0) && (colorA == b.colorA) && (colorB == b.colorB) && (colorC == b.colorC) && diff --git a/CodenameOne/src/com/codename1/ui/plaf/CSSBorder.java b/CodenameOne/src/com/codename1/ui/plaf/CSSBorder.java index 28c29fad21..581472aa0e 100644 --- a/CodenameOne/src/com/codename1/ui/plaf/CSSBorder.java +++ b/CodenameOne/src/com/codename1/ui/plaf/CSSBorder.java @@ -1345,7 +1345,7 @@ ScalarUnit copy() { public boolean equals(Object obj) { if (obj instanceof ScalarUnit) { ScalarUnit u = (ScalarUnit) obj; - return u.value == 0 && value == 0 || u.value == value && u.type == type; + return com.codename1.util.MathUtil.compare(u.value, 0f) == 0 && com.codename1.util.MathUtil.compare(value, 0f) == 0 || com.codename1.util.MathUtil.compare(u.value, value) == 0 && u.type == type; } return false; } diff --git a/CodenameOne/src/com/codename1/ui/plaf/RoundRectBorder.java b/CodenameOne/src/com/codename1/ui/plaf/RoundRectBorder.java index bbccbf78d0..a6c00522e0 100644 --- a/CodenameOne/src/com/codename1/ui/plaf/RoundRectBorder.java +++ b/CodenameOne/src/com/codename1/ui/plaf/RoundRectBorder.java @@ -330,7 +330,7 @@ public RoundRectBorder stroke(Stroke stroke) { */ public RoundRectBorder stroke(float stroke, boolean mm) { strokeThickness = stroke; - if (strokeThickness == 0) { + if (com.codename1.util.MathUtil.compare(strokeThickness, 0f) == 0) { this.stroke = null; return this; } @@ -350,7 +350,7 @@ public RoundRectBorder stroke(float stroke, boolean mm) { * @return border instance so these calls can be chained */ public RoundRectBorder shadowSpread(float shadowSpread) { - if (shadowSpread != this.shadowSpread) { + if (com.codename1.util.MathUtil.compare(shadowSpread, this.shadowSpread) != 0) { this.shadowSpread = shadowSpread; dirty = true; } @@ -404,7 +404,7 @@ public RoundRectBorder shadowColor(int shadowColor) { * @return border instance so these calls can be chained */ public RoundRectBorder shadowX(float shadowX) { - if (shadowX != this.shadowX) { + if (com.codename1.util.MathUtil.compare(shadowX, this.shadowX) != 0) { this.shadowX = shadowX; dirty = true; } @@ -418,7 +418,7 @@ public RoundRectBorder shadowX(float shadowX) { * @return border instance so these calls can be chained */ public RoundRectBorder shadowY(float shadowY) { - if (shadowY != this.shadowY) { + if (com.codename1.util.MathUtil.compare(shadowY, this.shadowY) != 0) { this.shadowY = shadowY; dirty = true; } @@ -432,7 +432,7 @@ public RoundRectBorder shadowY(float shadowY) { * @return border instance so these calls can be chained */ public RoundRectBorder shadowBlur(float shadowBlur) { - if (shadowBlur != this.shadowBlur) { + if (com.codename1.util.MathUtil.compare(shadowBlur, this.shadowBlur) != 0) { this.shadowBlur = shadowBlur; dirty = true; } @@ -446,7 +446,7 @@ public RoundRectBorder shadowBlur(float shadowBlur) { * @return border instance so these calls can be chained */ public RoundRectBorder cornerRadius(float cornerRadius) { - if (cornerRadius != this.cornerRadius) { + if (com.codename1.util.MathUtil.compare(cornerRadius, this.cornerRadius) != 0) { this.cornerRadius = cornerRadius; dirty = true; } diff --git a/CodenameOne/src/com/codename1/util/MathUtil.java b/CodenameOne/src/com/codename1/util/MathUtil.java index 5200c2702a..fb87436fff 100644 --- a/CodenameOne/src/com/codename1/util/MathUtil.java +++ b/CodenameOne/src/com/codename1/util/MathUtil.java @@ -1331,5 +1331,70 @@ public static long floor(double a) { return (long) a; } + /** + * Compares the two specified {@code float} values. The sign + * of the integer value returned is the same as that of the + * integer that would be returned by the call: + *
+     *    new Float(f1).compareTo(new Float(f2))
+     * 
+ * + * @param f1 the first {@code float} to compare. + * @param f2 the second {@code float} to compare. + * @return the value {@code 0} if {@code f1} is + * numerically equal to {@code f2}; a value less than + * {@code 0} if {@code f1} is numerically less than + * {@code f2}; and a value greater than {@code 0} + * if {@code f1} is numerically greater than + * {@code f2}. + * @since 1.4 + */ + public static int compare(float f1, float f2) { + if (f1 < f2) + return -1; // Neither val is NaN, thisVal is smaller + if (f1 > f2) + return 1; // Neither val is NaN, thisVal is larger + + // Cannot use floatToRawIntBits because of possibility of NaNs. + int thisBits = Float.floatToIntBits(f1); + int anotherBits = Float.floatToIntBits(f2); + + return (thisBits == anotherBits ? 0 : // Values are equal + (thisBits < anotherBits ? -1 : // (-0.0, 0.0) or (!NaN, NaN) + 1)); // (0.0, -0.0) or (NaN, !NaN) + } + + /** + * Compares the two specified {@code double} values. The sign + * of the integer value returned is the same as that of the + * integer that would be returned by the call: + *
+     *    new Double(d1).compareTo(new Double(d2))
+     * 
+ * + * @param d1 the first {@code double} to compare + * @param d2 the second {@code double} to compare + * @return the value {@code 0} if {@code d1} is + * numerically equal to {@code d2}; a value less than + * {@code 0} if {@code d1} is numerically less than + * {@code d2}; and a value greater than {@code 0} + * if {@code d1} is numerically greater than + * {@code d2}. + * @since 1.4 + */ + public static int compare(double d1, double d2) { + if (d1 < d2) + return -1; // Neither val is NaN, thisVal is smaller + if (d1 > d2) + return 1; // Neither val is NaN, thisVal is larger + + // Cannot use doubleToRawLongBits because of possibility of NaNs. + long thisBits = Double.doubleToLongBits(d1); + long anotherBits = Double.doubleToLongBits(d2); + + return (thisBits == anotherBits ? 0 : // Values are equal + (thisBits < anotherBits ? -1 : // (-0.0, 0.0) or (!NaN, NaN) + 1)); // (0.0, -0.0) or (NaN, !NaN) + } }