Menu

On Cygwin with g++ 10.2.0: Tests GeoConvert1[678] fail

Anonymous
2021-01-02
2021-02-05
  • Anonymous

    Anonymous - 2021-01-02

    Running the GeographicLib-1.51 tests on Windows 7 with

    $ uname -a
    CYGWIN_NT-6.1-WOW Annette-Pc2 3.1.7(0.340/5/3) 2020-08-22 19:03 i686 Cygwin
    $ g++ --version
    g++ (GCC) 10.2.0
    

    the following failures happen:

    $ ARGS='--verbose -R GeoConvert1[678]' make test
    Running tests...
    UpdateCTestConfiguration  from :/cygdrive/c/Thorkil/test/GeographicLib/work_20201231_1009/GeographicLib-1.51/BUILDWithCmake/DartConfiguration.tcl
    UpdateCTestConfiguration  from :/cygdrive/c/Thorkil/test/GeographicLib/work_20201231_1009/GeographicLib-1.51/BUILDWithCmake/DartConfiguration.tcl
    Test project /cygdrive/c/Thorkil/test/GeographicLib/work_20201231_1009/GeographicLib-1.51/BUILDWithCmake
    Constructing a list of tests
    Done constructing a list of tests
    Updating test list for fixtures
    Added 0 tests to meet fixture requirements
    Checking test dependency graph...
    Checking test dependency graph end
    test 17
        Start 17: GeoConvert16
    
    17: Test command: /cygdrive/c/Thorkil/test/GeographicLib/work_20201231_1009/GeographicLib-1.51/BUILDWithCmake/tools/GeoConvert.exe "-m" "-p" "3" "--input-string" "38n 444140.6 3684706.3"
    17: Test timeout computed to be: 10000000
    17: 38SMB4414059984706299
    1/3 Test #17: GeoConvert16 .....................***Failed  Required regular expression not found. Regex=[38SMB4414060084706300
    ]  0.13 sec
    test 18
        Start 18: GeoConvert17
    
    18: Test command: /cygdrive/c/Thorkil/test/GeographicLib/work_20201231_1009/GeographicLib-1.51/BUILDWithCmake/tools/GeoConvert.exe "-m" "-p" "3" "--input-string" "38n 500000 63.811"
    18: Test timeout computed to be: 10000000
    18: 38NNF0000000000063810
    2/3 Test #18: GeoConvert17 .....................***Failed  Required regular expression not found. Regex=[38NNF0000000000063811
    ]  0.14 sec
    test 19
        Start 19: GeoConvert18
    
    19: Test command: /cygdrive/c/Thorkil/test/GeographicLib/work_20201231_1009/GeographicLib-1.51/BUILDWithCmake/tools/GeoConvert.exe "-m" "-p" "4" "--input-string" "38n 500000 63.811"
    19: Test timeout computed to be: 10000000
    19: 38NNF000000000000638109
    3/3 Test #19: GeoConvert18 .....................***Failed  Required regular expression not found. Regex=[38NNF000000000000638110
    ]  0.12 sec
    
    0% tests passed, 3 tests failed out of 3
    
    Total Test time (real) =   0.48 sec
    
    The following tests FAILED:
             17 - GeoConvert16 (Failed)
             18 - GeoConvert17 (Failed)
             19 - GeoConvert18 (Failed)
    Errors while running CTest
    make: *** [Makefile:128: test] Error 8
    $
    

    Not a significant problem for me, to be sure.

    Best regards
    Thorkil Naur

     
  • Charles Karney

    Charles Karney - 2021-01-02

    The digits in the MGRS representation are given by multiplying x and y
    by 1000000 (to convert to microns) and taking the floor of the result.
    The expectation is that the multiplication and the floor produce
    correctly rounded results. E.g.,

    y = 63.811
    z = 1000000 * y
    floor(z)
    

    should give 63811000. Evidentally with your setup, you're getting
    63810999. This might be consequence of z being held in a register with
    extra precision; so it's possible that declaring z to be volatile will
    fix this. Perhaps g++ 10 needs a flag to modify this behavior. I can't
    really explore this until I get my hands on a system with g++ 10. So I
    propose to table this issue for now.

    If you want to probe deeper with the information I've provided that
    would be great.

     
  • Anonymous

    Anonymous - 2021-02-04

    Using gdb, the value of y, entered by interpreting the string "63.811", is displayed as follows, after being loaded into an extended double precision register in the x87 floating point unit:

    st0            63.8109999999999999432 (raw 0x4004ff3e76c8b4395800)
    

    The value of y*1000000 is displayed as follows:

    st0            63810999.9999999999418 (raw 0x4018f36b6dfffffffff0)
    

    So it is understandable that floor()ing this value results in the undesired result, 63810999.

    It appears that round() should be used rather than floor() in MGRS::Forward(...) as follows:

    76,77c76,77
    <       ix = (long long)(floor(x * mult_)),
    <       iy = (long long)(floor(y * mult_)),
    ---
    >       ix = (long long)(round(x * mult_)),
    >       iy = (long long)(round(y * mult_)),
    

    With this adjustment, the failing tests succeed.

    The question remains, of course, why these tests ever succeed without the adjustment. To answer that, I am in the fortunate situation that under different circumstances, namely

    $ uname -a
    Linux tn41.thorkilnaur.dk 5.6.13-100.fc30.i686 #1 SMP Fri May 15 00:02:53 UTC 2020 i686 i686 i386 GNU/Linux
    $ g++ --version
    g++ (GCC) 9.3.1 20200408 (Red Hat 9.3.1-2)
    Copyright (C) 2019 Free Software Foundation, Inc.
    This is free software; see the source for copying conditions.  There is NO
    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    $
    

    the tests actually do succeed.

    So looking into the details under these Linux circumstances, I find, using gdb, that the values of y and y*1000000 are displayed as follows

    st0            63.8109999999999999432 (raw 0x4004ff3e76c8b4395800)
    st0            63810999.9999999999418 (raw 0x4018f36b6dfffffffff0)
    

    so exactly the same as under Cygwin.

    The difference comes about when the floor() of y*1000000 is to be calculated: Under Cygwin, the floor() is calculated with inline code (the x87 frndint instruction with the RC rounding control set to "round down toward minus infinity"), resulting in the undesired value 63810999.

    Under Linux, in contrast, a floor() function is called to perform this calculation. And the parameter of this function is passed in memory which means that the value held in the 80-bit extended precision floating point register must be stored as an ordinary 64-bit double prior to the call. And storing implies rounding to the shorter fraction of the 64-bit double.

    The 80-bit value of y*1000000 is interpreted in binary as follows, grouping the bits into 1-bit sign, 15-bit exponent, 1-bit integer part, and 63-bit fraction (see the Intel manual "SECTION 8.2 X87 FPU DATA TYPES" for details about these floating point formats):

    0 100000000011000 1 111001101101011011011011111111111111111111111111111111111110000
    

    The 64-bit value stored for this is displayed as follows as a byte sequence, the least significant byte first:

    0xbfffe1c0: 0x00 0x00 0x00 0xc0 0x6d 0x6d 0x8e 0x41
    

    This sequence is interpreted as follows, grouping into 1-bit sign, 11-bit exponent, 0-bit (implied) integer part, and 52-bit fraction:

    0 10000011000 (1) 1110011011010110110111000000000000000000000000000000
    

    Aligning the fractions

    111001101101011011011011111111111111111111111111111111111110000
    1110011011010110110111000000000000000000000000000000
    

    we can see what has happened: Rounding to the nearest 52-bit fraction has actually resulted in an integer, rather than a value slightly less than an integer. And hence, the floor() calculates the desired result. A lucky case, one may suppose.

    In any case, the suggested adjustment works for the Linux circumstances as well: The tests still succeed with the adjustment applied.

    Regards
    Thorkil Naur

     
  • Charles Karney

    Charles Karney - 2021-02-04

    Indeed the problem will occur if floor is inlined and the argument y * mult_ is not rounded to a double.

    However your proposed solution, replacing floor by round won't work. Then you'll get the wrong answer for

    echo 38n 500000 63.8110008 | GeoConvert -m -p 6
    38NNF0000000000000063811000
    

    round will make the last digit 1. Check the documentation for MGRS::Forward.

    I will also note that if you compile GeographicLib to use long double instead of double (with cmake -D GEOGRAPHICLIB_PRECISION=3 ...), then floor(63.811L * 1000000) gives 63811000 consistent with the behavior I expect for double.

    So the "right" way to fix this problem is with something like

    volatile real xx = x * muilt_;
    long long is = (long long)(floor(xx));
    

    My hesitation about making this change is that the problem doesn't occur with g++ 10.2.1 on Linux. So presumably the failure you're seeing requires a rather specific set of conditions. Could you tell me what preprocessor symbols I can use to check for Cygwin?

     
  • Anonymous

    Anonymous - 2021-02-05

    Indeed, using round() instead of floor() was a mistake on my side, sorry about that.

    Nevertheless, I hesitate to attribute the problem to any specific Cygwin circumstances. I am presently more inclined to say that we must accept both results (61811000 and 61810999) of calculating floor(y*1000000) with y=61.811: When excess precision is used, "it is unpredictable when rounding to the types specified in the source code takes place" (quoting the man g++ description of the -fexcess-precision=style option). And whether and when such rounding takes place clearly affects the outcome of the floor() calculation.

    In an attempt to shed some light on this, I have distilled what seems to be the essence of the matter into the attached test program mfTest.cpp: Interpretation of a string into a floating point value, followed by the calculation of floor() of that value, after multiplying by 1000000.

    Running this program:

    $ uname -a
    Linux tn41.thorkilnaur.dk 5.6.13-100.fc30.i686 #1 SMP Fri May 15 00:02:53 UTC 2020 i686 i686 i386 GNU/Linux
    $ g++ --version
    g++ (GCC) 9.3.1 20200408 (Red Hat 9.3.1-2)
    Copyright (C) 2019 Free Software Foundation, Inc.
    This is free software; see the source for copying conditions.  There is NO
    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    $ g++ mfTest.cpp -O3 -o mfTest
    $ ./mfTest 61.811
    mfTest: 2021-Feb-05 07.17
    mainT: sizeof( double ) = 8, prec = 20
    megaFloor( t="double", s="61.811" ): floor( 61.810999999999999943 * 1000000 ) = 61810999
    mainT: sizeof( long double ) = 12, prec = 25
    megaFloor( t="long double", s="61.811" ): floor( 61.81099999999999999866773 * 1000000 ) = 61811000
    $
    

    The precisions used when presenting the floating point values are excessive, of course. Nevertheless, I trust that presenting the floating point value resulting from the interpretation of the string "61.811" as "61.8109999..." indicates that the floating point value is strictly less than exactly 61.811. Hence, the double calculation could in this case be judged "correct" and the long double calculation "incorrect". But the situation is, in my judgement, that both results are equally acceptable.

    The unpredictability of the calculations is illustrated by repeating the run of the program, now compiled with -O0 instead of -O3:

    $ g++ mfTest.cpp -O0 -o mfTest
    $ ./mfTest 61.811
    mfTest: 2021-Feb-05 07.17
    mainT: sizeof( double ) = 8, prec = 20
    megaFloor( t="double", s="61.811" ): floor( 61.810999999999999943 * 1000000 ) = 61811000
    mainT: sizeof( long double ) = 12, prec = 25
    megaFloor( t="long double", s="61.811" ): floor( 61.81099999999999999866773 * 1000000 ) = 61811000
    $
    

    In this case, both the double calculation and the long double calculation could be judged "incorrect".

    To avoid depending on unpredictabilities, one way would be to simply adjust a test such as GeoConvert17 to use --input-string "38n 500000 63.8115", adding a guard digit "5" to the input y value.

    Using this idea, I have tried:

    $ diff -ru ../tools/original/tests.cmake ../tools/
    --- ../tools/original/tests.cmake   2020-11-22 15:00:22.000000000 +0100
    +++ ../tools/tests.cmake    2021-02-05 11:08:20.000000000 +0100
    @@ -76,14 +76,14 @@
    
     # Check MGRS::Forward improved rounding fix, 2015-07-22
     add_test (NAME GeoConvert16 COMMAND GeoConvert
    -  -m -p 3 --input-string "38n 444140.6 3684706.3")
    +  -m -p 3 --input-string "38n 444140.6005 3684706.3005")
     set_tests_properties (GeoConvert16
       PROPERTIES PASS_REGULAR_EXPRESSION "38SMB4414060084706300")
     # Check MGRS::Forward digit consistency fix, 2015-07-23
     add_test (NAME GeoConvert17 COMMAND GeoConvert
    -  -m -p 3 --input-string "38n 500000 63.811")
    +  -m -p 3 --input-string "38n 500000 63.8115")
     add_test (NAME GeoConvert18 COMMAND GeoConvert
    -  -m -p 4 --input-string "38n 500000 63.811")
    +  -m -p 4 --input-string "38n 500000 63.81105")
     set_tests_properties (GeoConvert17
       PROPERTIES PASS_REGULAR_EXPRESSION "38NNF0000000000063811")
     set_tests_properties (GeoConvert18
    $
    

    With these changes, all tests succeed on both Cygwin and Linux.

    Best regards
    Thorkil Naur

     

Anonymous
Anonymous

Add attachments
Cancel