admin管理员组

文章数量:1305239

g++ gives grossly wrong numerical answers under some conditions. All this is 100% reproducible chez moi.

It fails differently depending on optimization level. It fails on my Ubuntu 24.04.1 LTS "noble" x86_64 platform ... and it fails in exactly the same way on an older "jammy" platform. The clang++ compiler on x86 is even worse; OTOH clang++ on an arm64 platform gets the right answers.

Here is some code that exercises the bugs:

#include <iostream>
#include <iomanip>
#include <sstream>
#include <vector>
using namespace std;
// android: error: __float128 is not supported on this target
typedef long double LD;

#define Literally(x) #x
#define Quote(X) Literally(X)
#define join(x,y) x ## y
#define append_L(x) join(x,L)

#include <cmath>

#if defined(__x86_64)
  string arch("__x86_64");
#elif defined(__ARM_ARCH_ISA_A64)
  string arch("__ARM_ARCH_ISA_A64");
#else
  string arch("undetermined");
#endif

#if defined(__OPTIMIZE__)
  string optimizing("Optimizing");
#else
  string optimizing("notOptimizing");
#endif

LD pi(3.14159265358979323846264338327950288419716939937510L);

double smash(LD const arg){
  return double(arg);
}

string fmt(LD const _val);

string pretty(string const arg) {
  return ((arg[0] == '-') ? "" : " ") + arg;
}

// Let's make sure compiler knows about long doubles.
// If this fails, you may need a compiler option
// such as -mlong-double-128 or the like.
static_assert (1.L + 1e-34L > 1.L, "need 128-bit long doubles");

#define munge(fun,arg,shift,goodval)            \
  { cout << "  ***  munging: "                  \
        << Quote(fun(arg))                      \
        << " shift " Quote(shift) << endl;      \
    cout << "should be:  " << pretty(Quote(goodval)) << endl;         \
    LD qqq = append_L(goodval);                               \
    if(shift) cout << "shifted:    " << fmt(qqq+shift) << endl;   \
    auto foo = fun(LD(arg));                        \
    LD bar; bar = fun(LD(arg));                     \
    cout << "initialize: " << fmt(foo)                  \
        << "  Δ: " << smash(foo - qqq) << endl;        \
    cout << "assign:     " << fmt(bar)                  \
        << "  Δ: " << smash(bar - qqq) << endl;        \
    vector<LD> vec; vec.push_back(arg);                 \
    auto vvv = fun(LD(vec.front()));                        \
    cout << "vector:     " << fmt(vvv)                  \
        << "  Δ: " << smash(vvv - qqq) << endl;        \
  }

int main(int argc, char * const * argv) {
  cout << "architecture: " << arch
        << "  version: " << Quote(__VERSION__)
        << "  " << optimizing
        << endl;
  munge(atan,1.0L,0,7.85398163397448309615660845819875699e-01)
  munge(sin,1.0L,0,8.41470984807896506652502321630299051e-01)
  munge(sin,pi - 1e-12,0,9.99999999999999979886734180690249881e-13)
  munge(cos,pi - 1e-12,1,-9.99999999999999999999999499999999955e-01)
  munge(log,1.L + 1e-20,0,9.99999999999994678933579770361535981e-21)
}

/////////////////////////////////////////////

LD power(LD const base, LD const pow) {
  if (pow != floor(smash(pow))) {
    cout << ">>> " << smash(pow) << endl;
    cout << ">>> " << floor(smash(pow)) << endl;
    throw runtime_error("Sorry, can't take non-integer power");
  }
  long int powmag = pow >= 0 ? pow : -pow;
  LD rover = base;
  LD rslt = 1.L;
  for (int ii = 0; ii < 32; ii++) {
    if (powmag == 0) break;
    if (ii) {
      rover *= rover;
    }
    if ((powmag & 1L)) {
      rslt *= rover;
    }
    powmag >>= 1;
  }
  return (pow >= 0) ? rslt : 1.L / rslt;
}

string fmt(LD const _val) {
  LD val(_val >= 0 ? _val : -_val);
  if (isinf(smash(val))) return "inf";
  stringstream rslt;
  rslt << ((_val >= 0) ? " " : "-");

// fails if number is too small to represent as a double:
  int exx = 0;
  if (val < 0.L) throw runtime_error("wtf?");
  if (val == 0.L) {
// don't try to normalize 0
  } else {
    LD acc(val);
    while (acc < 1) {
      acc *= 1.e5L;
      exx -= 5;
    }
    exx += floor(log10(smash(acc)));
    if (exx < -4932) {
      val = 1e-10;
    } else {
      LD scale = power(10.L, -exx);
      if (scale == 0) {
        cout << "_val: " << smash(val) << endl;
        cout << "val: " << smash(_val) << " eq: " << (val == 0) << endl;
        cout << "exx: " << exx << endl;
        cout << "scale: " << smash(scale) << endl;
        throw runtime_error("Sorry, too big for me");
      }
      val *= scale;
    }
    if (val < 0.L) throw runtime_error("WTF? < 0");
    if (val > 10.L) {
      cout << "??? " << exx << endl;
      throw runtime_error("WTF? > 1.e6");
    }
  }

  for (int ii = 0; ii < 36; ii++) {
    int foo = val;
    if (foo < 0) throw runtime_error("WTF??");
    rslt << int(foo);
    if (ii == 0) rslt << ".";
    LD sub = LD(foo);
    val -= sub;
    val *= 10.L;
  }
  if (exx) {
    string sign = exx < 0 ? "-" : "";
    rslt << "e" << setfill('0') << sign << setw(2) << abs(exx);
  }

  string penult = rslt.str();
  int pad = 43 - penult.length();
  if (pad > 0) penult += string(pad, ' ');
  return penult;
}

By way of background: It works just fine on an arm64 platform using clang, so my code is not completely crazy. It can be compiled with: :; clang++ float128.c

Here is the output when things are working properly. You can see that all the Deltas are zero, as they should be:

architecture: __ARM_ARCH_ISA_A64  version: "Clang 19.1.7"  notOptimizing
  ***  munging: atan(1.0L) shift 0
should be:   7.85398163397448309615660845819875699e-01
initialize:  7.85398163397448309615660845819875699e-01   Δ: 0
assign:      7.85398163397448309615660845819875699e-01   Δ: 0
vector:      7.85398163397448309615660845819875699e-01   Δ: 0
  ***  munging: sin(1.0L) shift 0
should be:   8.41470984807896506652502321630299051e-01
initialize:  8.41470984807896506652502321630298992e-01   Δ: 0
assign:      8.41470984807896506652502321630298992e-01   Δ: 0
vector:      8.41470984807896506652502321630298992e-01   Δ: 0
  ***  munging: sin(pi - 1e-12) shift 0
should be:   9.99999999999999979886734180690249881e-13
initialize:  0.99999999999999997988673418069024986e-12   Δ: 0
assign:      0.99999999999999997988673418069024986e-12   Δ: 0
vector:      0.99999999999999997988673418069024986e-12   Δ: 0
  ***  munging: cos(pi - 1e-12) shift 1
should be:  -9.99999999999999999999999499999999955e-01
shifted:     5.00000000044794469975736991122850022e-25 
initialize: -0.99999999999999999999999949999999993       Δ: 0
assign:     -0.99999999999999999999999949999999993       Δ: 0
vector:     -0.99999999999999999999999949999999993       Δ: 0
  ***  munging: log(1.L + 1e-20) shift 0
should be:   9.99999999999994678933579770361535981e-21
initialize:  9.99999999999994678933579770361536055e-21   Δ: 0
assign:      9.99999999999994678933579770361536055e-21   Δ: 0
vector:      9.99999999999994678933579770361536055e-21   Δ: 0

Things are not so good on my Ubuntu 24.04.1 LTS "noble" x86_64 platform ... and it fails in exactly the same way on an older "jammy" platform. It's compiled with: :; g++ -O3 -g -Wall -Wstrict-overflow=2 -std=c++23 -mlong-double-128 float128.c

Here is the buggy output:

architecture: __x86_64  version: "11.4.0"  Optimizing
  ***  munging: atan(1.0L) shift 0
should be:   7.85398163397448309615660845819875699e-01
initialize:  7.85398163397448309615660845819875699e-01   Δ: 0
assign:      7.85398163397448309615660845819875699e-01   Δ: 0
vector:      1.00000000000000000000000000000000000       Δ: 0.214602
  ***  munging: sin(1.0L) shift 0
should be:   8.41470984807896506652502321630299051e-01
initialize:  8.41470984807896506652502321630298992e-01   Δ: -9.62965e-35
assign:      8.41470984807896506652502321630298992e-01   Δ: -9.62965e-35
vector:      1.00000000000000000000000000000000000       Δ: 0.158529
  ***  munging: sin(pi - 1e-12) shift 0
should be:   9.99999999999999979886734180690249881e-13
initialize:  3.14159265358879323846264338329961614       Δ: 3.14159
assign:      3.14159265358879323846264338329961614       Δ: 3.14159
vector:      3.14159265358879323846264338329961614       Δ: 3.14159
  ***  munging: cos(pi - 1e-12) shift 1
should be:  -9.99999999999999999999999499999999955e-01
shifted:     5.00000000044794469975736991122850022e-25 
initialize:  3.14159265358879323846264338329961614       Δ: 4.14159
assign:      3.14159265358879323846264338329961614       Δ: 4.14159
vector:      3.14159265358879323846264338329961614       Δ: 4.14159
  ***  munging: log(1.L + 1e-20) shift 0
should be:   9.99999999999994678933579770361535981e-21
initialize:  9.99999999999994678933579770361536055e-21   Δ: 0
assign:      9.99999999999994678933579770361536055e-21   Δ: 0
vector:      1.00000000000000000000999999999999994       Δ: 1

It fails differently depending on optimization level, compiled with: :; g++ -O0 -g -Wall -Wstrict-overflow=2 -std=c++23 -mlong-double-128 float128.c

Here is the differently-buggy output:

architecture: __x86_64  version: "11.4.0"  notOptimizing
  ***  munging: atan(1.0L) shift 0
should be:   7.85398163397448309615660845819875699e-01
initialize:  7.85398163397448309615660845819875699e-01   Δ: 0
assign:      1.00000000000000000000000000000000000       Δ: 0.214602
vector:      1.00000000000000000000000000000000000       Δ: 0.214602
  ***  munging: sin(1.0L) shift 0
should be:   8.41470984807896506652502321630299051e-01
initialize:  8.41470984807896506652502321630298992e-01   Δ: -9.62965e-35
assign:      1.00000000000000000000000000000000000       Δ: 0.158529
vector:      1.00000000000000000000000000000000000       Δ: 0.158529
  ***  munging: sin(pi - 1e-12) shift 0
should be:   9.99999999999999979886734180690249881e-13
initialize:  0.00000000010000000000000000364321973e-4947  Δ: -1e-12
assign:      0.00000000010000000000000000364321973e-4947  Δ: -1e-12
vector:      0.00000000010000000000000000364321973e-4947  Δ: -1e-12
  ***  munging: cos(pi - 1e-12) shift 1
should be:  -9.99999999999999999999999499999999955e-01
shifted:     5.00000000044794469975736991122850022e-25 
initialize:  0.00000000010000000000000000364321973e-4947  Δ: 1
assign:      0.00000000010000000000000000364321973e-4947  Δ: 1
vector:      0.00000000010000000000000000364321973e-4947  Δ: 1
  ***  munging: log(1.L + 1e-20) shift 0
should be:   9.99999999999994678933579770361535981e-21
initialize:  9.99999999999994678933579770361536055e-21   Δ: 0
assign:      1.00000000000000000000999999999999994       Δ: 1
vector:      1.00000000000000000000999999999999994       Δ: 1

=========================================

If you simplify the example very much, it fails differently or not at all.

So, questions: Is anybody else seeing this? Is there any way to obtain reliable results? Would it help to rewrite all my code to call the quadmath library functions, rather than the cmath functions?

本文标签: g gives wrong answersfloat128 aka long doublecmathStack Overflow