LLVM Bugzilla is read-only and represents the historical archive of all LLVM issues filled before November 26, 2021. Use github to submit LLVM bugs

Bug 24539 - Conversion of APFloat and string and back can change value
Summary: Conversion of APFloat and string and back can change value
Status: NEW
Alias: None
Product: libraries
Classification: Unclassified
Component: Support Libraries (show other bugs)
Version: trunk
Hardware: PC Linux
: P normal
Assignee: Unassigned LLVM Bugs
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2015-08-21 13:07 PDT by Dan Liew
Modified: 2019-11-18 04:39 PST (History)
4 users (show)

See Also:
Fixed By Commit(s):


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Dan Liew 2015-08-21 13:07:17 PDT
This was discussed on mailing list [1].

The bug seems to be that the conversion of an APFloat to a string (using the "natural precision") and then converting that back into an APFloat can result in a different value when not using ``APFloat::rmNearestTiesToEven``. 

In the implementation of ``APFloat::toString(...)`` you can
specify ``FormatPrecision`` as 0. The method comments state that this
will use the "natural precision" of the number. In the actual
implementation the comments say that when FormatPrecision is 0 that

```
// We use enough digits so the number can be round-tripped back to an
// APFloat. The formula comes from "How to Print Floating-Point Numbers
// Accurately" by Steele and White.
```

Based on the above comments I expected to be able to convert an
APFloat to a string and back again when ``FormatPrecision`` is set to
zero. However this does not seem to hold. Here's some example code
that demonstrates this.

```
#include "llvm/Support/raw_ostream.h"
#include "llvm/ADT/APFloat.h"
#include <string>

using namespace llvm;

std::string getString(APFloat f) {
  SmallVector<char,10> strRep;
  // FormatPrecision=0 means that the "natural precision" of the number is used
  f.toString(strRep,/*FormatPrecision=*/0, /*FormatMaxPadding=*/0);
  return std::string(strRep.begin(), strRep.end());
}

uint16_t getBits(APFloat f) {
  APInt bits = f.bitcastToAPInt();
  assert(bits.getActiveBits() <= 16);
  return (uint16_t) (bits.getZExtValue() & 0xffff);
}

int main(int argc, char** argv) {
  APFloat f(APFloat::IEEEhalf);
  APFloat newF(APFloat::IEEEhalf);
  f.convertFromString("0.3", APFloat::rmTowardZero);
  outs() << "f bits: 0x";
  outs().write_hex(getBits(f));
  outs() << "\n";
  assert(getBits(f) == 0x34cc);
  // Check that if we get the string using FormatPrecision=0
  // that this can be used to construct another APFloat of the
  // same value.
  std::string fAsString = getString(f);
  outs() << "f as string: \"" << fAsString << "\"\n";
  newF.convertFromString(fAsString, APFloat::rmTowardZero);
  outs() << "newF as string: \"" << getString(newF) << "\"\n";
  outs() << "newF bits: 0x";
  outs().write_hex(getBits(newF));
  outs() << "\n";
  // BUG This assert fails
  assert(getBits(newF) == 0x34cc);
  return 0;
}
```

The output I see is

```
f bits: 0x34cc
f as string: "2.998E-1"
newF as string: "2.9956E-1"
newF bits: 0x34cb
... Assertion `getBits(newF) == 0x34cc' failed.
```

As you can see when we create a new APFloat from the string we get a
slightly smaller number. I have observed that if I use
``APFloat::rmNearestTiesToEven`` when creating ``newF`` that I do get
an APFloat instance which has the same value as the original APFloat.


[1] http://lists.llvm.org/pipermail/llvm-dev/2015-August/089085.html
Comment 1 Ehud Katz 2019-11-18 04:39:14 PST
This is the proper behavior.
Even when using the native rounding modes, we get, when running the following program:

```c++
#include <cstdio>
#include <cfenv>
#include <sstream>
#include <string>
using namespace std;

string to_str (float f) {
  ostringstream os;
  os.precision(9);
  os << f;
  return os.str ();
}

double d = 0.3;

int main() {
    float f;
    string str;

    fesetround(FE_TONEAREST);
    f = d;
    str = to_string(f);
    printf("%08x = %.9g -> %s\n", *(int*)&f, f, str.c_str());
    f = stof(str);
    printf("%08x = %.9g\n", *(int*)&f, f);

    fesetround(FE_TOWARDZERO);
    f = d;
    str = to_string(f);
    printf("%08x = %.9g -> %s\n", *(int*)&f, f, str.c_str());
    f = stof(str);
    printf("%08x = %.9g\n", *(int*)&f, f);
}
```

The output is:

3e99999a = 0.300000012 -> 0.300000012
3e99999a = 0.300000012
3e999999 = 0.299999982 -> 0.299999982
3e999998 = 0.299999952

Which is exactly as in APFloat (for IEEEsingle).