Skip to content

Improve mutlipleOf validator with floating point value. #190

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

Merged
merged 1 commit into from
Jan 4, 2015
Merged

Improve mutlipleOf validator with floating point value. #190

merged 1 commit into from
Jan 4, 2015

Conversation

vpelletier
Copy link
Contributor

Example:

>>> 150.0 % 0.01
0.009999999999996878
>>> 0.01 - 0.009999999999996878
3.1225022567582528e-15
>>> 150.0 / 0.01
15000.0

I've prepared a commit against json-schema/JSON-Schema-Test-Suite adding following test case:

    {
        "schema": {"multipleOf": 0.01},
        "tests": [
            {
                "description": "150.0 is multiple of 0.01",
                "data": 150.0,
                "valid": true
            },
            {
                "description": "150.0001 is not multiple of 0.01",
                "data": 150.0001,
                "valid": false
            }
        ]
    }

but am not sure it is the right place to put such test nor how to choose "better" values without relying on implementation (does implementatio use modulo ? on double or single precision ? etc). As a result, I could not come up with a meaningful description. Anyway, this test fails with current implementation with:
ValidationError: 150.0 is not a multiple of 0.01
and is fixed with attached patch.

Rely on system's built-in float tolerance by comparing quotient with cast
quotient instead of checking distance to remainder.
@vpelletier
Copy link
Contributor Author

Anything wrong with this change ?

@Julian
Copy link
Member

Julian commented Dec 31, 2014

Hey, sorry, been a bit backed up but appreciate the patch certainly. I'm not sure, I have to think about it a bit more. It seems the answer is no, nothing wrong :), because all the tests pass, but I have to look closer since I haven't convinced myself that this actually doesn't just shift the imprecision someplace else.

@vpelletier
Copy link
Contributor Author

Good point, I'm not really sure of this either actually.
At least for 150.0-family values (ie, same mantisa but varying exponent) /-division seems to consistently behave well while remainder precision gets worse as exponent increases.
Adding a small-ish value gives expected results up to the point where adding 0.00001 doesn't actually change the value.
I do not know if this can be extrapolated to other mantises.

150.0 * 2**x

>>> print '\n'.join(['%20.5f /:%30.12f %5s %%:%.18f %5s' % (y, y/0.01, int(y/0.01) != y/0.01, y%0.01, (y%0.01 > FLOAT_TOLERANCE) and (0.01 - y%0.01) > FLOAT_TOLERANCE) for y in [150 * 2**x for x in xrange(40)]])
           150.00000 /:            15000.000000000000 False %:0.009999999999996878  True
           300.00000 /:            30000.000000000000 False %:0.009999999999993755  True
           600.00000 /:            60000.000000000000 False %:0.009999999999987510  True
          1200.00000 /:           120000.000000000000 False %:0.009999999999975020  True
          2400.00000 /:           240000.000000000000 False %:0.009999999999950040  True
          4800.00000 /:           480000.000000000000 False %:0.009999999999900080  True
          9600.00000 /:           960000.000000000000 False %:0.009999999999800160  True
         19200.00000 /:          1920000.000000000000 False %:0.009999999999600320  True
         38400.00000 /:          3840000.000000000000 False %:0.009999999999200640  True
         76800.00000 /:          7680000.000000000000 False %:0.009999999998401279  True
        153600.00000 /:         15360000.000000000000 False %:0.009999999996802558  True
        307200.00000 /:         30720000.000000000000 False %:0.009999999993605116  True
        614400.00000 /:         61440000.000000000000 False %:0.009999999987210231  True
       1228800.00000 /:        122880000.000000000000 False %:0.009999999974420462  True
       2457600.00000 /:        245760000.000000000000 False %:0.009999999948840923  True
       4915200.00000 /:        491520000.000000000000 False %:0.009999999897681846  True
       9830400.00000 /:        983040000.000000000000 False %:0.009999999795363692  True
      19660800.00000 /:       1966080000.000000000000 False %:0.009999999590727384  True
      39321600.00000 /:       3932160000.000000000000 False %:0.009999999181454769  True
      78643200.00000 /:       7864320000.000000000000 False %:0.009999998362909537  True
     157286400.00000 /:      15728640000.000000000000 False %:0.009999996725819074  True
     314572800.00000 /:      31457280000.000000000000 False %:0.009999993451638147  True
     629145600.00000 /:      62914560000.000000000000 False %:0.009999986903276295  True
    1258291200.00000 /:     125829120000.000000000000 False %:0.009999973806552589  True
    2516582400.00000 /:     251658240000.000000000000 False %:0.009999947613105178  True
    5033164800.00000 /:     503316480000.000000000000 False %:0.009999895226210356  True
   10066329600.00000 /:    1006632960000.000000000000 False %:0.009999790452420712  True
   20132659200.00000 /:    2013265920000.000000000000 False %:0.009999580904841423  True
   40265318400.00000 /:    4026531840000.000000000000 False %:0.009999161809682846  True
   80530636800.00000 /:    8053063680000.000000000000 False %:0.009998323619365692  True
  161061273600.00000 /:   16106127360000.000000000000 False %:0.009996647238731384  True
  322122547200.00000 /:   32212254720000.000000000000 False %:0.009993294477462769  True
  644245094400.00000 /:   64424509440000.000000000000 False %:0.009986588954925537  True
 1288490188800.00000 /:  128849018880000.000000000000 False %:0.009973177909851074  True
 2576980377600.00000 /:  257698037760000.000000000000 False %:0.009946355819702149  True
 5153960755200.00000 /:  515396075520000.000000000000 False %:0.009892711639404297  True
10307921510400.00000 /: 1030792151040000.000000000000 False %:0.009785423278808594  True
20615843020800.00000 /: 2061584302080000.000000000000 False %:0.009570846557617188  True
41231686041600.00000 /: 4123168604160000.000000000000 False %:0.009141693115234375  True
82463372083200.00000 /: 8246337208320000.000000000000 False %:0.008283386230468750  True

0.00001 + 150.0 * 2**x

>>> print '\n'.join(['%20.5f /:%30.12f %5s %%:%.18f %5s' % (y, y/0.01, int(y/0.01) != y/0.01, y%0.01, (y%0.01 > FLOAT_TOLERANCE) and (0.01 - y%0.01) > FLOAT_TOLERANCE) for y in [0.00001 + 150 * 2**x for x in xrange(40)]])
           150.00001 /:            15000.001000000000  True %:0.000010000000000052  True
           300.00001 /:            30000.000999999997  True %:0.000009999999968507  True
           600.00001 /:            60000.000999999997  True %:0.000009999999962262  True
          1200.00001 /:           120000.000999999989  True %:0.000009999999949772  True
          2400.00001 /:           240000.001000000018  True %:0.000010000000152166  True
          4800.00001 /:           480000.000999999989  True %:0.000009999999647459  True
          9600.00001 /:           960000.000999999931  True %:0.000009999999547539  True
         19200.00001 /:          1920000.000999999931  True %:0.000009999999347698  True
         38400.00001 /:          3840000.001000000164  True %:0.000010000002585997  True
         76800.00001 /:          7680000.001000000164  True %:0.000010000001786636  True
        153600.00001 /:         15360000.000999998301  True %:0.000009999985636000  True
        307200.00001 /:         30720000.001000002027  True %:0.000010000011542388  True
        614400.00001 /:         61440000.000999994576  True %:0.000009999946939843  True
       1228800.00001 /:        122880000.001000002027  True %:0.000010000050565395  True
       2457600.00001 /:        245760000.001000016928  True %:0.000010000024985857  True
       4915200.00001 /:        491520000.000999927521  True %:0.000009999508165492  True
       9830400.00001 /:        983040000.001000046730  True %:0.000010000337169913  True
      19660800.00001 /:       1966080000.000999927521  True %:0.000009998269888456  True
      39321600.00001 /:       3932160000.000999927521  True %:0.000009997860615840  True
      78643200.00001 /:       7864320000.000999450684  True %:0.000009997042070609  True
     157286400.00001 /:      15728640000.001001358032  True %:0.000010010306141339  True
     314572800.00001 /:      31457280000.000999450684  True %:0.000010007031960413  True
     629145600.00001 /:      62914560000.000999450684  True %:0.000010000483598560  True
    1258291200.00001 /:     125829120000.000991821289  True %:0.000009987386874855  True
    2516582400.00001 /:     251658240000.001007080078  True %:0.000009961193427444  True
    5033164800.00001 /:     503316480000.000915527344  True %:0.000009431969374418  True
   10066329600.00001 /:    1006632960000.000976562500  True %:0.000009327195584774  True
   20132659200.00001 /:    2013265920000.001220703125  True %:0.000011024996638298  True
   40265318400.00001 /:    4026531840000.000488281250  True %:0.000006791204214096  True
   80530636800.00002 /:    8053063680000.000976562500  True %:0.000013582408428192  True
  161061273600.00000 /:   16106127360000.000000000000 False %:0.009996647238731384  True
  322122547200.00000 /:   32212254720000.000000000000 False %:0.009993294477462769  True
  644245094400.00000 /:   64424509440000.000000000000 False %:0.009986588954925537  True
 1288490188800.00000 /:  128849018880000.000000000000 False %:0.009973177909851074  True
 2576980377600.00000 /:  257698037760000.000000000000 False %:0.009946355819702149  True
 5153960755200.00000 /:  515396075520000.000000000000 False %:0.009892711639404297  True
10307921510400.00000 /: 1030792151040000.000000000000 False %:0.009785423278808594  True
20615843020800.00000 /: 2061584302080000.000000000000 False %:0.009570846557617188  True
41231686041600.00000 /: 4123168604160000.000000000000 False %:0.009141693115234375  True
82463372083200.00000 /: 8246337208320000.000000000000 False %:0.008283386230468750  True

@Julian
Copy link
Member

Julian commented Jan 4, 2015

OK, think you've convinced me for now that this is better than what's there at least, and whenever my brain gets a minute to relax I can try to think a bit harder about it, but merging, appreciated!

Julian added a commit that referenced this pull request Jan 4, 2015
Improve mutlipleOf validator with floating point value.
@Julian Julian merged commit e1c3023 into python-jsonschema:master Jan 4, 2015
@vpelletier
Copy link
Contributor Author

I'm sorry, I just found a case where my code doesn't work:

> 10.1/0.1
100.99999999999999

I've not yet decided where I wan to go from there. While I understand there is no golden bullet for floats, I would like such "simple" (only 4 significant digits in result...) cases to just work.

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.

2 participants