Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Added Grisu3 algorithm support for double.ToString(). #14646

Merged
merged 6 commits into from
Jan 29, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/classlibnative/bcltype/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ set(BCLTYPE_SOURCES
bignum.cpp
currency.cpp
decimal.cpp
diyfp.cpp
grisu3.cpp
windowsruntimebufferhelper.cpp
number.cpp
oavariant.cpp
Expand Down
75 changes: 75 additions & 0 deletions src/classlibnative/bcltype/diyfp.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
//
// File: diyfp.cpp
//

//

#include "diyfp.h"
#include "fp.h"

void DiyFp::Minus(const DiyFp& rhs)
{
_ASSERTE(m_e == rhs.e());
_ASSERTE(m_f >= rhs.f());

m_f -= rhs.f();
}

void DiyFp::Minus(const DiyFp& left, const DiyFp& right, DiyFp& result)
{
result = left;
result.Minus(right);
}

void DiyFp::Multiply(const DiyFp& rhs)
{
UINT64 m32 = 0xFFFFFFFF;

UINT64 a = m_f >> 32;
UINT64 b = m_f & m32;
UINT64 c = rhs.f() >> 32;
UINT64 d = rhs.f() & m32;

UINT64 ac = a * c;
UINT64 bc = b * c;
UINT64 ad = a * d;
UINT64 bd = b * d;

UINT64 tmp = (bd >> 32) + (ad & m32) + (bc & m32);
tmp += 1U << 31;

m_f = ac + (ad >> 32) + (bc >> 32) + (tmp >> 32);
m_e = m_e + rhs.e() + SIGNIFICAND_LENGTH;
}

void DiyFp::Multiply(const DiyFp& left, const DiyFp& right, DiyFp& result)
{
result = left;
result.Multiply(right);
}

void DiyFp::GenerateNormalizedDiyFp(double value, DiyFp& result)
{
_ASSERTE(value > 0.0);

UINT64 f = 0;
int e = 0;
ExtractFractionAndBiasedExponent(value, &f, &e);

UINT64 normalizeBit = (UINT64)1 << 52;
while ((f & normalizeBit) == 0)
{
f <<= 1;
--e;
}

int lengthDiff = DiyFp::SIGNIFICAND_LENGTH - 53;
f <<= lengthDiff;
e -= lengthDiff;

result.SetSignificand(f);
result.SetExponent(e);
}
93 changes: 93 additions & 0 deletions src/classlibnative/bcltype/diyfp.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
//
// File: diyfp.h
//

//

#ifndef _DIYFP_H
#define _DIYFP_H

#include <clrtypes.h>

// An exteneded floating-point data structure which is required by Grisu3 algorithm.
// It defines a 64-bit significand and a 32-bit exponent,
// which is EXTENDED compare to IEEE double precision floating-point number (53 bits significand and 11 bits exponent).
//
// Original Grisu algorithm produces suboptimal results. To shorten the output (which is part of Grisu2/Grisu3's job),
// we need additional 11 bits of the significand f and larger exponent e (A larger exponent range is used to avoid overflow. A 32-bit exponent is far big enough).
// To fully understand how Grisu3 uses this data structure to get better result, please read http://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf
class DiyFp
{
public:
DiyFp()
: m_f(0), m_e()
{
}

DiyFp(UINT64 f, int e)
: m_f(f), m_e(e)
{
}

DiyFp(const DiyFp& rhs)
: m_f(rhs.m_f), m_e(rhs.m_e)
{
}

DiyFp& operator=(const DiyFp& rhs)
{
m_f = rhs.m_f;
m_e = rhs.m_e;

return *this;
}

UINT64 f() const
{
return m_f;
}

int e() const
{
return m_e;
}

void SetSignificand(UINT64 f)
{
m_f = f;
}

void SetExponent(int e)
{
m_e = e;
}

void Minus(const DiyFp& rhs);
static void Minus(const DiyFp& left, const DiyFp& right, DiyFp& result);

void Multiply(const DiyFp& rhs);
static void Multiply(const DiyFp& left, const DiyFp& right, DiyFp& result);

static void GenerateNormalizedDiyFp(double value, DiyFp& result);

public:
static const int SIGNIFICAND_LENGTH = 64;

private:
// Extended significand.
// IEEE 754 double-precision numbers only require 53 bits significand.
// However, in Grisu3 we need additional 11 bits so we declare m_f as UINT64.
// Please note m_f does not include sign bit.
UINT64 m_f;

// Extended exponent.
// IEEE 754 double-precision numbers only require 11 bits exponent.
// However, in Grisu3 we need extra space to avoid overflow so we declare m_e as int.
// Please note m_e is a biased exponent if the DiyFp instance is generated by GenerateNormalizedDiyFp().
int m_e;
};

#endif
67 changes: 67 additions & 0 deletions src/classlibnative/bcltype/fp.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
//
// File: fp.h
//

//

#ifndef _FP_H
#define _FP_H

#include <clrtypes.h>

struct FPSINGLE {
#if BIGENDIAN
unsigned int sign: 1;
unsigned int exp: 8;
unsigned int mant: 23;
#else
unsigned int mant: 23;
unsigned int exp: 8;
unsigned int sign: 1;
#endif
};

struct FPDOUBLE {
#if BIGENDIAN
unsigned int sign: 1;
unsigned int exp: 11;
unsigned int mantHi: 20;
unsigned int mantLo;
#else
unsigned int mantLo;
unsigned int mantHi: 20;
unsigned int exp: 11;
unsigned int sign: 1;
#endif
};

static void ExtractFractionAndBiasedExponent(double value, UINT64* f, int* e)
{
if (((FPDOUBLE*)&value)->exp != 0)
{
// For normalized value, according to https://en.wikipedia.org/wiki/Double-precision_floating-point_format
Copy link
Member

@tannergooding tannergooding Jan 4, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is missing the sign handling and the case where the implicit significand bit is 0.

Edit: Nevermind, I see the case where the implicit significant bit is 0 is below.

// value = 1.fraction * 2^(exp - 1023)
// = (1 + mantissa / 2^52) * 2^(exp - 1023)
// = (2^52 + mantissa) * 2^(exp - 1023 - 52)
//
// So f = (2^52 + mantissa), e = exp - 1075;
*f = ((UINT64)(((FPDOUBLE*)&value)->mantHi) << 32) | ((FPDOUBLE*)&value)->mantLo + ((UINT64)1 << 52);
*e = ((FPDOUBLE*)&value)->exp - 1075;
}
else
{
// For denormalized value, according to https://en.wikipedia.org/wiki/Double-precision_floating-point_format
// value = 0.fraction * 2^(1 - 1023)
// = (mantissa / 2^52) * 2^(-1022)
// = mantissa * 2^(-1022 - 52)
// = mantissa * 2^(-1074)
// So f = mantissa, e = -1074
*f = ((UINT64)(((FPDOUBLE*)&value)->mantHi) << 32) | ((FPDOUBLE*)&value)->mantLo;
*e = -1074;
}
}

#endif // _FP_H
Loading