Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate Rational numbers with smaller denominators #296

Merged
merged 1 commit into from
Sep 20, 2021
Merged

Generate Rational numbers with smaller denominators #296

merged 1 commit into from
Sep 20, 2021

Conversation

Bodigrim
Copy link
Contributor

Closes #295, making maximal denominator dependent on size instead of being fixed to 9999999999999.

> sample (arbitrarySizedFractional :: Gen Rational)
0 % 1
(-1) % 1
3 % 4
4 % 3
(-1) % 1
8 % 5
1 % 1
(-3) % 2
4 % 7
(-2) % 3
(-1) % 1

precision = 9999999999999 :: Integer
sized $ \n -> do
numer <- chooseInt (-n, n)
denom <- chooseInt (1, max 1 n)
Copy link
Contributor

@phadej phadej May 16, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't generate Rational larger han 1 does it?

Copy link
Contributor

@phadej phadej May 16, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh it does, but it's harder to see what kind of distribution we get. Can't we just limit the precision to max 256 size or something like that, in the original code?

Copy link
Contributor Author

@Bodigrim Bodigrim May 16, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, sometimes you may want to generate numerators bigger than 256. I have such applications, for instance.

The comment does not guarantee anything about distribution beyond "The number can be positive or negative and its maximum absolute value depends on the size parameter", which still holds. Even the range of distribution remains unchanged.

This implementation is also consistent with smallcheck instance for Ratio.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With

 arbitrarySizedFractional =
   sized $ \n ->
     let n' = toInteger n in
-      do b <- chooseInteger (1, precision)
+      do b <- chooseInteger (1, max 256 n')
          a <- chooseInteger ((-n') * b, n' * b)
          return (fromRational (a % b))
- where
-  precision = 9999999999999 :: Integer

one gets

0 % 1
(-7) % 39
505 % 129
(-349) % 98
825 % 124
1599 % 242
(-1187) % 246
(-219) % 80
(-414) % 31
1293 % 97
1103 % 158

--

Yet, I'd against this change alone (especially to fix #295). The Double and Float ought to have own hand written instances. @cartazio's idea to have half like above and half using full Double / Float precision is good. There are plenty of numerical bugs to be discovered by IEEE rounding issues e.g. generating (only) coarse numbers will make some users unhappy as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What IEEE rounding issues do you have in mind? The proposed implementation is perfectly capable to show that associativity does not hold for Double, for example:

> quickCheck $ \a b c -> a + (b + c) === ((a + b) + c :: Double)
*** Failed! Falsified (after 12 tests and 6 shrinks):
-0.2
1.0
-0.1
0.7 /= 0.7000000000000001

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not mind Arbitrary Double to be not just arbitrary = arbitrarySizedFractional, but rather oneof [arbitrarySizedFractional, somethingElse], but deciding on somethingElse is out of scope of this particular PR, which aims to improve arbitrarySizedFractional first of all.

Your approach with max 256 n' excludes (almost all) short ratios from being generated. I would disagree that this is beneficial for any Fractional. This is undesirable even for Double: e. g., your generator is unsuitable for testing rounding half to even, because it almost never generates numbers of form ddd.5.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your distribution looks like

Screenshot from 2020-05-16 20-49-59

So I don't see how it would generate ddd.5 numbers either often enough. Yet, true, we should generate such numbers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Err, I'm not sure what facts about half-integers can be derived from this plot (and what distribution you desire to obtain). As quoted above, sample generates at least some half-integers, on contrary to the existing implementation and to your suggestion.

phadej added a commit to phadej/quickcheck that referenced this pull request May 16, 2020
Tries to address comments in nick8325#295
and nick8325#296

Doesn't change how arbitrarySizedFractional works.
Now it is only used for (Ratio a) instance.

Also two properties which should fail everytime:
the Double generator has a chance of generating same values,
and it also hits values which break addition associativity.
phadej added a commit to phadej/quickcheck that referenced this pull request May 16, 2020
Tries to address comments in nick8325#295
and nick8325#296

Doesn't change how arbitrarySizedFractional works.
Now it is only used for (Ratio a) instance.

Also two properties which should fail everytime:
the Double generator has a chance of generating same values,
and it also hits values which break addition associativity.
@cartazio
Copy link

cartazio commented May 16, 2020 via email

phadej added a commit to phadej/quickcheck that referenced this pull request Jul 6, 2020
Tries to address comments in nick8325#295
and nick8325#296

Doesn't change how arbitrarySizedFractional works.
Now it is only used for (Ratio a) instance.

Also two properties which should fail everytime:
the Double generator has a chance of generating same values,
and it also hits values which break addition associativity.
@Bodigrim
Copy link
Contributor Author

As discussed above, @phadej and I are tackling different aspects of the issue. However, after some consideration, I came to a conclusion that it is futile to improve arbitrarySizedFractional: it does not make much sense for Fractionals other than Double or Rational. E. g., it returns unbounded by size results for modular arithmetic or Galois fields. And given that #297 does a very good job for Double, the only thing left is Rational. That said, I decided to repurpose this PR to improving instance Arbitrary (Ratio a).

@Bodigrim Bodigrim changed the title Generate Fractional numbers with smaller denominators Generate Rational numbers with smaller denominators Jul 18, 2020
@Bodigrim
Copy link
Contributor Author

@nick8325 just a gentle reminder about this PR.

@Bodigrim
Copy link
Contributor Author

@nick8325 ping

1 similar comment
@Bodigrim
Copy link
Contributor Author

@nick8325 ping

@nick8325 nick8325 merged commit ec4f84c into nick8325:master Sep 20, 2021
@nick8325
Copy link
Owner

Sorry about the unreasonably-long delay, but it's merged now. Thanks for the patch!

I tweaked the generator to do this:

  sized $ \n -> do
    denom <- chooseInt (1, max 1 n)
    numer <- chooseInt (-n*denom, n*denom)
    pure $ fromIntegral numer / fromIntegral denom

so that the generated numbers are drawn roughly uniformly from the interval -n...n, and have a maximum denominator of n. That is, increasing the size increases both the magnitude and the maximum denominator. I hope that's reasonable but let me know if you see some problem with it.

@Bodigrim
Copy link
Contributor Author

@nick8325 that's probably fine, thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Size parameter for float / double instances doesn’t behave the same as in other fixed size types
4 participants