From 5653651c0deb0d812e9e7861c083da81861fc237 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Sat, 25 Mar 2017 12:31:19 -0400 Subject: [PATCH] Update score calculation The value of a score is now a double. For boolean habits, this number goes from zero to one and corresponds to the percentage. For numerical habits, it now corresponds to a weighted average of the checkmark values. Also, for non-daily boolean habits, the score now increases with implicit checkmarks. --- app/build.gradle | 2 +- .../views/CheckmarkWidgetViewTest.java | 2 +- app/src/main/assets/migrations/17.sql | 5 + .../activities/common/views/ScoreChart.java | 11 +- .../habits/list/views/HabitCardView.java | 2 +- .../habits/show/views/OverviewCard.java | 14 +- .../isoron/uhabits/io/HabitsCSVExporter.java | 2 +- .../java/org/isoron/uhabits/models/Score.java | 26 +--- .../org/isoron/uhabits/models/ScoreList.java | 22 +-- .../uhabits/widgets/CheckmarkWidget.java | 2 +- .../commands/EditHabitCommandTest.java | 4 +- .../isoron/uhabits/models/ScoreListTest.java | 143 +++++++++--------- .../org/isoron/uhabits/models/ScoreTest.java | 64 +++----- 13 files changed, 141 insertions(+), 158 deletions(-) create mode 100644 app/src/main/assets/migrations/17.sql diff --git a/app/build.gradle b/app/build.gradle index 649ab0f0..6c85a232 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,7 +12,7 @@ android { minSdkVersion 15 targetSdkVersion 25 - buildConfigField "Integer", "databaseVersion", "16" + buildConfigField "Integer", "databaseVersion", "17" buildConfigField "String", "databaseFilename", "\"uhabits.db\"" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" diff --git a/app/src/androidTest/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetViewTest.java index 6f338079..65c3fbef 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetViewTest.java @@ -51,7 +51,7 @@ public class CheckmarkWidgetViewTest extends BaseViewTest view = new CheckmarkWidgetView(targetContext); int color = ColorUtils.getAndroidTestColor(habit.getColor()); double score = habit.getScores().getTodayValue(); - float percentage = (float) score / Score.MAX_VALUE; + float percentage = (float) score; view.setActiveColor(color); view.setCheckmarkValue(habit.getCheckmarks().getTodayValue()); diff --git a/app/src/main/assets/migrations/17.sql b/app/src/main/assets/migrations/17.sql new file mode 100644 index 00000000..9c9a9b40 --- /dev/null +++ b/app/src/main/assets/migrations/17.sql @@ -0,0 +1,5 @@ +DROP TABLE Score; +CREATE TABLE Score (Id INTEGER PRIMARY KEY AUTOINCREMENT, habit INTEGER REFERENCES Habits(Id), score REAL, timestamp INTEGER); +CREATE INDEX idx_score_habit_timestamp on score(habit, timestamp); +delete from Streak; +delete from Checkmarks; \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/activities/common/views/ScoreChart.java b/app/src/main/java/org/isoron/uhabits/activities/common/views/ScoreChart.java index 191fe819..fe6c38b4 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/common/views/ScoreChart.java +++ b/app/src/main/java/org/isoron/uhabits/activities/common/views/ScoreChart.java @@ -108,15 +108,15 @@ public class ScoreChart extends ScrollableChart Random random = new Random(); scores = new LinkedList<>(); - int previous = Score.MAX_VALUE / 2; + double previous = 0.5f; long timestamp = DateUtils.getStartOfToday(); long day = DateUtils.millisecondsInOneDay; for (int i = 1; i < 100; i++) { - int step = Score.MAX_VALUE / 10; - int current = previous + random.nextInt(step * 2) - step; - current = Math.max(0, Math.min(Score.MAX_VALUE, current)); + double step = 0.1f; + double current = previous + random.nextDouble() * step * 2 - step; + current = Math.max(0, Math.min(1.0f, current)); scores.add(new Score(timestamp, current)); previous = current; timestamp -= day; @@ -190,8 +190,7 @@ public class ScoreChart extends ScrollableChart double score = scores.get(offset).getValue(); long timestamp = scores.get(offset).getTimestamp(); - double relativeScore = score / Score.MAX_VALUE; - int height = (int) (columnHeight * relativeScore); + int height = (int) (columnHeight * score); rect.set(0, 0, baseSize, baseSize); rect.offset(k * columnWidth + (columnWidth - baseSize) / 2, diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.java index 6415a959..5c6bebdf 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.java @@ -140,7 +140,7 @@ public class HabitCardView extends FrameLayout public void setScore(double score) { - float percentage = (float) score / Score.MAX_VALUE; + float percentage = (float) score; scoreRing.setPercentage(percentage); scoreRing.setPrecision(1.0f / 16); postInvalidate(); diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/show/views/OverviewCard.java b/app/src/main/java/org/isoron/uhabits/activities/habits/show/views/OverviewCard.java index db423054..080c8e4c 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/show/views/OverviewCard.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/show/views/OverviewCard.java @@ -105,9 +105,9 @@ public class OverviewCard extends HabitCard private void initEditMode() { color = ColorUtils.getAndroidTestColor(1); - cache.todayScore = Score.MAX_VALUE * 0.6f; - cache.lastMonthScore = Score.MAX_VALUE * 0.42f; - cache.lastYearScore = Score.MAX_VALUE * 0.75f; + cache.todayScore = 0.6f; + cache.lastMonthScore = 0.42f; + cache.lastYearScore = 0.75f; refreshColors(); refreshScore(); } @@ -121,11 +121,9 @@ public class OverviewCard extends HabitCard private void refreshScore() { - float todayPercentage = cache.todayScore / Score.MAX_VALUE; - float monthDiff = - todayPercentage - (cache.lastMonthScore / Score.MAX_VALUE); - float yearDiff = - todayPercentage - (cache.lastYearScore / Score.MAX_VALUE); + float todayPercentage = cache.todayScore; + float monthDiff = todayPercentage - cache.lastMonthScore; + float yearDiff = todayPercentage - cache.lastYearScore; scoreRing.setPercentage(todayPercentage); scoreLabel.setText(String.format("%.0f%%", todayPercentage * 100)); diff --git a/app/src/main/java/org/isoron/uhabits/io/HabitsCSVExporter.java b/app/src/main/java/org/isoron/uhabits/io/HabitsCSVExporter.java index 997323c3..d89413d2 100644 --- a/app/src/main/java/org/isoron/uhabits/io/HabitsCSVExporter.java +++ b/app/src/main/java/org/isoron/uhabits/io/HabitsCSVExporter.java @@ -202,7 +202,7 @@ public class HabitsCSVExporter checksWriter.write(String.valueOf(checkmarks.get(j)[i])); checksWriter.write(DELIMITER); String score = - String.format("%.4f", ((float) scores.get(j)[i]) / Score.MAX_VALUE); + String.format("%.4f", ((float) scores.get(j)[i])); scoresWriter.write(score); scoresWriter.write(DELIMITER); } diff --git a/app/src/main/java/org/isoron/uhabits/models/Score.java b/app/src/main/java/org/isoron/uhabits/models/Score.java index 05591e04..bc27b9da 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Score.java +++ b/app/src/main/java/org/isoron/uhabits/models/Score.java @@ -21,16 +21,13 @@ package org.isoron.uhabits.models; import org.apache.commons.lang3.builder.*; +import static java.lang.Math.*; + /** * Represents how strong a habit is at a certain date. */ public final class Score { - /** - * Maximum score value attainable by any habit. - */ - public static final int MAX_VALUE = 19259478; - /** * Timestamp of the day to which this score applies. Time of day should be * midnight (UTC). @@ -55,27 +52,20 @@ public final class Score * The frequency of the habit is the number of repetitions divided by the * length of the interval. For example, a habit that should be repeated 3 * times in 8 days has frequency 3.0 / 8.0 = 0.375. - *

- * The checkmarkValue should be UNCHECKED, CHECKED_IMPLICITLY or - * CHECK_EXPLICITLY. * * @param frequency the frequency of the habit * @param previousScore the previous score of the habit * @param checkmarkValue the value of the current checkmark * @return the current score */ - public static int compute(double frequency, - double previousScore, - int checkmarkValue) + public static double compute(double frequency, + double previousScore, + int checkmarkValue) { - double multiplier = Math.pow(0.5, 1.0 / (14.0 / frequency - 1)); - int score = (int) (previousScore * multiplier); + double multiplier = pow(0.5, frequency / 13.0); - if (checkmarkValue == Checkmark.CHECKED_EXPLICITLY) - { - score += 1000000; - score = Math.min(score, Score.MAX_VALUE); - } + double score = previousScore * multiplier; + score += checkmarkValue * (1 - multiplier); return score; } diff --git a/app/src/main/java/org/isoron/uhabits/models/ScoreList.java b/app/src/main/java/org/isoron/uhabits/models/ScoreList.java index d4c57cfe..e6021956 100644 --- a/app/src/main/java/org/isoron/uhabits/models/ScoreList.java +++ b/app/src/main/java/org/isoron/uhabits/models/ScoreList.java @@ -132,7 +132,7 @@ public abstract class ScoreList implements Iterable public List groupBy(DateUtils.TruncateField field) { computeAll(); - HashMap> groups = getGroupedValues(field); + HashMap> groups = getGroupedValues(field); List scores = groupsToAvgScores(groups); Collections.sort(scores, (s1, s2) -> s2.compareNewer(s1)); return scores; @@ -173,7 +173,7 @@ public abstract class ScoreList implements Iterable { String timestamp = dateFormat.format(s.getTimestamp()); String score = - String.format("%.4f", ((float) s.getValue()) / Score.MAX_VALUE); + String.format("%.4f", s.getValue()); out.write(String.format("%s,%s\n", timestamp, score)); } } @@ -277,6 +277,8 @@ public abstract class ScoreList implements Iterable for (int i = 0; i < checkmarkValues.length; i++) { int value = checkmarkValues[checkmarkValues.length - i - 1]; + if(!habit.isNumerical() && value > 0) value = 1; + previousValue = Score.compute(freq, previousValue, value); scores.add(new Score(from + day * i, previousValue)); } @@ -285,9 +287,9 @@ public abstract class ScoreList implements Iterable } @NonNull - private HashMap> getGroupedValues(DateUtils.TruncateField field) + private HashMap> getGroupedValues(DateUtils.TruncateField field) { - HashMap> groups = new HashMap<>(); + HashMap> groups = new HashMap<>(); for (Score s : this) { @@ -296,26 +298,26 @@ public abstract class ScoreList implements Iterable if (!groups.containsKey(groupTimestamp)) groups.put(groupTimestamp, new ArrayList<>()); - groups.get(groupTimestamp).add((long) s.getValue()); + groups.get(groupTimestamp).add(s.getValue()); } return groups; } @NonNull - private List groupsToAvgScores(HashMap> groups) + private List groupsToAvgScores(HashMap> groups) { List scores = new LinkedList<>(); for (Long timestamp : groups.keySet()) { - long meanValue = 0L; - ArrayList groupValues = groups.get(timestamp); + double meanValue = 0.0; + ArrayList groupValues = groups.get(timestamp); - for (Long v : groupValues) meanValue += v; + for (Double v : groupValues) meanValue += v; meanValue /= groupValues.size(); - scores.add(new Score(timestamp, (int) meanValue)); + scores.add(new Score(timestamp, meanValue)); } return scores; diff --git a/app/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidget.java b/app/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidget.java index c6953e93..aa57e737 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidget.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidget.java @@ -53,7 +53,7 @@ public class CheckmarkWidget extends BaseWidget CheckmarkWidgetView view = (CheckmarkWidgetView) v; int color = ColorUtils.getColor(getContext(), habit.getColor()); double score = habit.getScores().getTodayValue(); - float percentage = (float) score / Score.MAX_VALUE; + float percentage = (float) score; int checkmark = habit.getCheckmarks().getTodayValue(); view.setPercentage(percentage); diff --git a/app/src/test/java/org/isoron/uhabits/commands/EditHabitCommandTest.java b/app/src/test/java/org/isoron/uhabits/commands/EditHabitCommandTest.java index bf5b1da1..0345d34c 100644 --- a/app/src/test/java/org/isoron/uhabits/commands/EditHabitCommandTest.java +++ b/app/src/test/java/org/isoron/uhabits/commands/EditHabitCommandTest.java @@ -87,7 +87,7 @@ public class EditHabitCommandTest extends BaseUnitTest command.execute(); assertThat(habit.getName(), equalTo("modified")); assertThat(habit.getScores().getTodayValue(), - greaterThan(originalScore)); + lessThan(originalScore)); command.undo(); assertThat(habit.getName(), equalTo("original")); @@ -96,6 +96,6 @@ public class EditHabitCommandTest extends BaseUnitTest command.execute(); assertThat(habit.getName(), equalTo("modified")); assertThat(habit.getScores().getTodayValue(), - greaterThan(originalScore)); + lessThan(originalScore)); } } diff --git a/app/src/test/java/org/isoron/uhabits/models/ScoreListTest.java b/app/src/test/java/org/isoron/uhabits/models/ScoreListTest.java index 1485ea25..f0295ad0 100644 --- a/app/src/test/java/org/isoron/uhabits/models/ScoreListTest.java +++ b/app/src/test/java/org/isoron/uhabits/models/ScoreListTest.java @@ -28,9 +28,12 @@ import java.util.*; import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.number.IsCloseTo.closeTo; public class ScoreListTest extends BaseUnitTest { + private static final double E = 1e-6; + private Habit habit; @Override @@ -47,42 +50,39 @@ public class ScoreListTest extends BaseUnitTest toggleRepetitions(0, 20); double expectedValues[] = { - 12629351, - 12266245, - 11883254, - 11479288, - 11053198, - 10603773, - 10129735, - 9629735, - 9102352, - 8546087, - 7959357, - 7340494, - 6687738, - 5999234, - 5273023, - 4507040, - 3699107, - 2846927, - 1948077, - 1000000 + 0.655747, + 0.636894, + 0.617008, + 0.596033, + 0.573910, + 0.550574, + 0.525961, + 0.500000, + 0.472617, + 0.443734, + 0.413270, + 0.381137, + 0.347244, + 0.311495, + 0.273788, + 0.234017, + 0.192067, + 0.147820, + 0.101149, + 0.051922, }; - double actualValues[] = new double[expectedValues.length]; - int i = 0; for (Score s : habit.getScores()) - actualValues[i++] = s.getValue(); - - assertThat(actualValues, equalTo(expectedValues)); + assertThat(s.getValue(), closeTo(expectedValues[i++], E)); } @Test public void test_getTodayValue() { toggleRepetitions(0, 20); - assertThat(habit.getScores().getTodayValue(), equalTo(12629351.0)); + double actual = habit.getScores().getTodayValue(); + assertThat(actual, closeTo(0.655747, E)); } @Test @@ -91,36 +91,36 @@ public class ScoreListTest extends BaseUnitTest toggleRepetitions(0, 20); double expectedValues[] = { - 12629351, - 12266245, - 11883254, - 11479288, - 11053198, - 10603773, - 10129735, - 9629735, - 9102352, - 8546087, - 7959357, - 7340494, - 6687738, - 5999234, - 5273023, - 4507040, - 3699107, - 2846927, - 1948077, - 1000000, - 0, - 0, - 0 + 0.655747, + 0.636894, + 0.617008, + 0.596033, + 0.573910, + 0.550574, + 0.525961, + 0.500000, + 0.472617, + 0.443734, + 0.413270, + 0.381137, + 0.347244, + 0.311495, + 0.273788, + 0.234017, + 0.192067, + 0.147820, + 0.101149, + 0.051922, + 0.000000, + 0.000000, + 0.000000 }; ScoreList scores = habit.getScores(); long current = DateUtils.getStartOfToday(); for (double expectedValue : expectedValues) { - assertThat(scores.getValue(current), equalTo(expectedValue)); + assertThat(scores.getValue(current), closeTo(expectedValue, E)); current -= DateUtils.millisecondsInOneDay; } } @@ -133,23 +133,23 @@ public class ScoreListTest extends BaseUnitTest habit.getScores().groupBy(DateUtils.TruncateField.MONTH); assertThat(list.size(), equalTo(5)); - assertThat(list.get(0).getValue(), equalTo(14634077.0)); - assertThat(list.get(1).getValue(), equalTo(12969133.0)); - assertThat(list.get(2).getValue(), equalTo(10595391.0)); + assertThat(list.get(0).getValue(), closeTo(0.549096, E)); + assertThat(list.get(1).getValue(), closeTo(0.480098, E)); + assertThat(list.get(2).getValue(), closeTo(0.377885, E)); } @Test public void test_invalidateNewerThan() { - assertThat(habit.getScores().getTodayValue(), equalTo(0.0)); + assertThat(habit.getScores().getTodayValue(), closeTo(0.0, E)); toggleRepetitions(0, 2); - assertThat(habit.getScores().getTodayValue(), equalTo(1948077.0)); + assertThat(habit.getScores().getTodayValue(), closeTo(0.101149, E)); habit.setFrequency(new Frequency(1, 2)); habit.getScores().invalidateNewerThan(0); - assertThat(habit.getScores().getTodayValue(), equalTo(1974654.0)); + assertThat(habit.getScores().getTodayValue(), closeTo(0.051922, E)); } @Test @@ -157,16 +157,16 @@ public class ScoreListTest extends BaseUnitTest { Habit habit = fixtures.createShortHabit(); - String expectedCSV = "2015-01-25,0.2649\n" + - "2015-01-24,0.2205\n" + - "2015-01-23,0.2283\n" + - "2015-01-22,0.2364\n" + - "2015-01-21,0.1909\n" + - "2015-01-20,0.1439\n" + - "2015-01-19,0.0952\n" + - "2015-01-18,0.0986\n" + - "2015-01-17,0.1021\n" + - "2015-01-16,0.0519\n"; + String expectedCSV = "2015-01-25,0.2372\n" + + "2015-01-24,0.2096\n" + + "2015-01-23,0.2172\n" + + "2015-01-22,0.1889\n" + + "2015-01-21,0.1595\n" + + "2015-01-20,0.1291\n" + + "2015-01-19,0.0976\n" + + "2015-01-18,0.1011\n" + + "2015-01-17,0.0686\n" + + "2015-01-16,0.0349\n"; StringWriter writer = new StringWriter(); habit.getScores().writeCSV(writer); @@ -186,13 +186,16 @@ public class ScoreListTest extends BaseUnitTest long to = today - 2 * day; double[] expected = { - 11883254, - 11479288, - 11053198, + 0.617008, + 0.596033, + 0.573909, }; double[] actual = habit.getScores().getValues(from, to); - assertThat(actual, equalTo(expected)); + assertThat(actual.length, equalTo(expected.length)); + + for(int i = 0; i < actual.length; i++) + assertThat(actual[i], closeTo(expected[i], E)); } private void toggleRepetitions(final int from, final int to) diff --git a/app/src/test/java/org/isoron/uhabits/models/ScoreTest.java b/app/src/test/java/org/isoron/uhabits/models/ScoreTest.java index 34b40015..0a94ad03 100644 --- a/app/src/test/java/org/isoron/uhabits/models/ScoreTest.java +++ b/app/src/test/java/org/isoron/uhabits/models/ScoreTest.java @@ -19,15 +19,17 @@ package org.isoron.uhabits.models; -import org.isoron.uhabits.BaseUnitTest; -import org.junit.Before; -import org.junit.Test; +import org.isoron.uhabits.*; +import org.junit.*; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.junit.Assert.assertThat; +import static org.hamcrest.number.IsCloseTo.*; +import static org.isoron.uhabits.models.Score.*; +import static org.junit.Assert.*; public class ScoreTest extends BaseUnitTest { + private static final double E = 1e-6; + @Override @Before public void setUp() @@ -38,46 +40,30 @@ public class ScoreTest extends BaseUnitTest @Test public void test_compute_withDailyHabit() { - int checkmark = Checkmark.UNCHECKED; - assertThat(Score.compute(1, 0, checkmark), equalTo(0)); - assertThat(Score.compute(1, 5000000, checkmark), equalTo(4740387)); - assertThat(Score.compute(1, 10000000, checkmark), equalTo(9480775)); - assertThat(Score.compute(1, Score.MAX_VALUE, checkmark), - equalTo(18259478)); + int check = 1; + double freq = 1.0; + assertThat(compute(freq, 0, check), closeTo(0.051922, E)); + assertThat(compute(freq, 0.5, check), closeTo(0.525961, E)); + assertThat(compute(freq, 0.75, check), closeTo(0.762981, E)); - checkmark = Checkmark.CHECKED_IMPLICITLY; - assertThat(Score.compute(1, 0, checkmark), equalTo(0)); - assertThat(Score.compute(1, 5000000, checkmark), equalTo(4740387)); - assertThat(Score.compute(1, 10000000, checkmark), equalTo(9480775)); - assertThat(Score.compute(1, Score.MAX_VALUE, checkmark), - equalTo(18259478)); - - checkmark = Checkmark.CHECKED_EXPLICITLY; - assertThat(Score.compute(1, 0, checkmark), equalTo(1000000)); - assertThat(Score.compute(1, 5000000, checkmark), equalTo(5740387)); - assertThat(Score.compute(1, 10000000, checkmark), equalTo(10480775)); - assertThat(Score.compute(1, Score.MAX_VALUE, checkmark), - equalTo(Score.MAX_VALUE)); + check = 0; + assertThat(compute(freq, 0, check), closeTo(0, E)); + assertThat(compute(freq, 0.5, check), closeTo(0.474039, E)); + assertThat(compute(freq, 0.75, check), closeTo(0.711058, E)); } @Test public void test_compute_withNonDailyHabit() { - int checkmark = Checkmark.CHECKED_EXPLICITLY; - assertThat(Score.compute(1 / 3.0, 0, checkmark), equalTo(1000000)); - assertThat(Score.compute(1 / 3.0, 5000000, checkmark), - equalTo(5916180)); - assertThat(Score.compute(1 / 3.0, 10000000, checkmark), - equalTo(10832360)); - assertThat(Score.compute(1 / 3.0, Score.MAX_VALUE, checkmark), - equalTo(Score.MAX_VALUE)); + int check = 1; + double freq = 1 / 3.0; + assertThat(compute(freq, 0, check), closeTo(0.017616, E)); + assertThat(compute(freq, 0.5, check), closeTo(0.508808, E)); + assertThat(compute(freq, 0.75, check), closeTo(0.754404, E)); - assertThat(Score.compute(1 / 7.0, 0, checkmark), equalTo(1000000)); - assertThat(Score.compute(1 / 7.0, 5000000, checkmark), - equalTo(5964398)); - assertThat(Score.compute(1 / 7.0, 10000000, checkmark), - equalTo(10928796)); - assertThat(Score.compute(1 / 7.0, Score.MAX_VALUE, checkmark), - equalTo(Score.MAX_VALUE)); + check = 0; + assertThat(compute(freq, 0, check), closeTo(0.0, E)); + assertThat(compute(freq, 0.5, check), closeTo(0.491192, E)); + assertThat(compute(freq, 0.75, check), closeTo(0.736788, E)); } }