diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs
index f4e324d7ba..37b7d71d2b 100644
--- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs
+++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs
@@ -85,16 +85,6 @@ namespace osu.Game.Tests.NonVisual.Filtering
Assert.That(filterCriteria.SearchTerms[0].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.IsolatedPhrase));
}
- /*
- * The following tests have been written a bit strangely (they don't check exact
- * bound equality with what the filter says).
- * This is to account for floating-point arithmetic issues.
- * For example, specifying a bpm<140 filter would previously match beatmaps with BPM
- * of 139.99999, which would be displayed in the UI as 140.
- * Due to this the tests check the last tick inside the range and the first tick
- * outside of the range.
- */
-
[TestCase("star")]
[TestCase("stars")]
public void TestApplyStarQueries(string variant)
@@ -105,11 +95,31 @@ namespace osu.Game.Tests.NonVisual.Filtering
Assert.AreEqual("easy", filterCriteria.SearchText.Trim());
Assert.AreEqual(1, filterCriteria.SearchTerms.Length);
Assert.IsNotNull(filterCriteria.StarDifficulty.Max);
- Assert.Greater(filterCriteria.StarDifficulty.Max, 3.99d);
- Assert.Less(filterCriteria.StarDifficulty.Max, 4.00d);
+ Assert.AreEqual(filterCriteria.StarDifficulty.Max, 4.00d);
Assert.IsNull(filterCriteria.StarDifficulty.Min);
}
+ [Test]
+ public void TestStarQueriesInclusive()
+ {
+ const string query = $"stars>=6";
+ var filterCriteria = new FilterCriteria();
+ FilterQueryParser.ApplyQueries(filterCriteria, query);
+ Assert.AreEqual(filterCriteria.StarDifficulty.Min, 6.00d);
+ Assert.True(filterCriteria.StarDifficulty.IsLowerInclusive);
+ Assert.IsNull(filterCriteria.StarDifficulty.Max);
+ }
+
+ /*
+ * The following tests have been written a bit strangely (they don't check exact
+ * bound equality with what the filter says).
+ * This is to account for floating-point arithmetic issues.
+ * For example, specifying a bpm<140 filter would previously match beatmaps with BPM
+ * of 139.99999, which would be displayed in the UI as 140.
+ * Due to this the tests check the last tick inside the range and the first tick
+ * outside of the range.
+ */
+
[Test]
public void TestApplyApproachRateQueries()
{
diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs
index dc77b0101e..02b5eb5b7a 100644
--- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs
+++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs
@@ -5,6 +5,7 @@ using System;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Screens.Select.Filter;
+using osu.Game.Utils;
namespace osu.Game.Screens.Select.Carousel
{
@@ -59,7 +60,7 @@ namespace osu.Game.Screens.Select.Carousel
if (!match) return false;
- match &= !criteria.StarDifficulty.HasFilter || criteria.StarDifficulty.IsInRange(BeatmapInfo.StarRating);
+ match &= !criteria.StarDifficulty.HasFilter || criteria.StarDifficulty.IsInRange(BeatmapInfo.StarRating.FloorToDecimalDigits(2));
match &= !criteria.ApproachRate.HasFilter || criteria.ApproachRate.IsInRange(BeatmapInfo.Difficulty.ApproachRate);
match &= !criteria.DrainRate.HasFilter || criteria.DrainRate.IsInRange(BeatmapInfo.Difficulty.DrainRate);
match &= !criteria.CircleSize.HasFilter || criteria.CircleSize.IsInRange(BeatmapInfo.Difficulty.CircleSize);
diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs
index 1094d88730..02a6da146e 100644
--- a/osu.Game/Screens/Select/FilterQueryParser.cs
+++ b/osu.Game/Screens/Select/FilterQueryParser.cs
@@ -41,7 +41,7 @@ namespace osu.Game.Screens.Select
case "star":
case "stars":
case "sr":
- return TryUpdateCriteriaRange(ref criteria.StarDifficulty, op, value, 0.01d / 2);
+ return TryUpdateCriteriaRange(ref criteria.StarDifficulty, op, value, 0);
case "ar":
return TryUpdateCriteriaRange(ref criteria.ApproachRate, op, value);
@@ -309,6 +309,8 @@ namespace osu.Game.Screens.Select
case Operator.Equal:
range.Min = value - tolerance;
range.Max = value + tolerance;
+ if (tolerance == 0)
+ range.IsLowerInclusive = range.IsUpperInclusive = true;
break;
case Operator.Greater:
@@ -317,6 +319,8 @@ namespace osu.Game.Screens.Select
case Operator.GreaterOrEqual:
range.Min = value - tolerance;
+ if (tolerance == 0)
+ range.IsLowerInclusive = true;
break;
case Operator.Less:
@@ -325,6 +329,8 @@ namespace osu.Game.Screens.Select
case Operator.LessOrEqual:
range.Max = value + tolerance;
+ if (tolerance == 0)
+ range.IsUpperInclusive = true;
break;
}
diff --git a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterMatching.cs b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterMatching.cs
index a776b2f796..f2f246093d 100644
--- a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterMatching.cs
+++ b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterMatching.cs
@@ -9,6 +9,7 @@ using System.Threading.Tasks;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Carousel;
using osu.Game.Screens.Select;
+using osu.Game.Utils;
namespace osu.Game.Screens.SelectV2
{
@@ -81,7 +82,7 @@ namespace osu.Game.Screens.SelectV2
if (!match) return false;
- match &= !criteria.StarDifficulty.HasFilter || criteria.StarDifficulty.IsInRange(beatmap.StarRating);
+ match &= !criteria.StarDifficulty.HasFilter || criteria.StarDifficulty.IsInRange(beatmap.StarRating.FloorToDecimalDigits(2));
match &= !criteria.ApproachRate.HasFilter || criteria.ApproachRate.IsInRange(beatmap.Difficulty.ApproachRate);
match &= !criteria.DrainRate.HasFilter || criteria.DrainRate.IsInRange(beatmap.Difficulty.DrainRate);
match &= !criteria.CircleSize.HasFilter || criteria.CircleSize.IsInRange(beatmap.Difficulty.CircleSize);
diff --git a/osu.Game/Utils/FormatUtils.cs b/osu.Game/Utils/FormatUtils.cs
index fa7d6595e9..28776ea0bf 100644
--- a/osu.Game/Utils/FormatUtils.cs
+++ b/osu.Game/Utils/FormatUtils.cs
@@ -10,6 +10,12 @@ namespace osu.Game.Utils
{
public static class FormatUtils
{
+ public static double FloorToDecimalDigits(this double value, uint digits)
+ {
+ double base10 = Math.Pow(10, digits);
+ return Math.Floor(value * base10) / base10;
+ }
+
///
/// Turns the provided accuracy into a percentage with 2 decimal places.
///
@@ -21,9 +27,7 @@ namespace osu.Game.Utils
// ie. a score which gets 89.99999% shouldn't ever show as 90%.
// the reasoning for this is that cutoffs for grade increases are at whole numbers and displaying the required
// percentile with a non-matching grade is confusing.
- accuracy = Math.Floor(accuracy * 10000) / 10000;
-
- return accuracy.ToLocalisableString("0.00%");
+ return accuracy.FloorToDecimalDigits(4).ToLocalisableString("0.00%");
}
///
@@ -42,9 +46,7 @@ namespace osu.Game.Utils
// i.e. a beatmap which has a star rating of 6.9999* should never show as 7.00*.
// this matters for star rating medals which use hard cutoffs at whole numbers,
// which then confuses users when they beat a 6.9999* beatmap but don't get the 7-star medal.
- starRating = Math.Floor(starRating * 100) / 100;
-
- return starRating.ToLocalisableString("0.00");
+ return starRating.FloorToDecimalDigits(2).ToLocalisableString("0.00");
}
///