Bug or expected behavior of APFloat class?

Hi,

I've been playing around with the APFloat class lately and I came
across behavior I was not expecting based on reading the
implementation comments and I'm wondering if it's a bug or
intentional.

The behavior concerns converting an APFloat to a string and back
again. 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.
So I'm wondering if the comment about round tripping APFloats only
holds for certain rounding modes.

Any thoughts on this?

Thanks,
Dan

From: "Dan Liew via llvm-dev" <llvm-dev@lists.llvm.org>
To: llvm-dev@lists.llvm.org
Sent: Monday, August 10, 2015 1:05:59 PM
Subject: [llvm-dev] Bug or expected behavior of APFloat class?

Hi,

I've been playing around with the APFloat class lately and I came
across behavior I was not expecting based on reading the
implementation comments and I'm wondering if it's a bug or
intentional.

The behavior concerns converting an APFloat to a string and back
again. 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.
So I'm wondering if the comment about round tripping APFloats only
holds for certain rounding modes.

Any thoughts on this?

It is certainly possible that APFloat::rmNearestTiesToEven is required for the current code to work (that's the default mode that most of LLVM assumes). That certainly seems like a bug, and I'm not surprised if you find bugs when using other rounding modes.

-Hal

Any thoughts on this?

It is certainly possible that APFloat::rmNearestTiesToEven is required for the current code to work (that's the default mode that most of LLVM assumes). That certainly seems like a bug, and I'm not surprised if you find bugs when using other rounding modes.

Thanks for the reply. I don't really have time to look into the
details of this but I've opened up a bug report in case someone does
want to take a look at it in the future.

https://llvm.org/bugs/show_bug.cgi?id=24539