Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A DecimalFloatingPoint struct in the public side of the API #190

Closed
verelpode opened this issue Dec 5, 2020 · 1 comment
Closed

A DecimalFloatingPoint struct in the public side of the API #190

verelpode opened this issue Dec 5, 2020 · 1 comment

Comments

@verelpode
Copy link

Converting to a string is ofcourse useful, but the non-string decimal representation is also quite useful to have. Therefore, following I've written a little extension to the Ryu source code that provides people with a friendly API for converting a float64/double number to a decimal floating-point representation in the form of a clean struct that represents:

Significand * (10 ** Exponent)

Fundamentally this is the same as what Ryu already did, but the difference is that I suggest that the Ryu source code include this ability in the public side of the API, not only as an internal function, because this ability is quite useful.

The following is written in C#. It's easy to convert this to the equivalent in C++ or plain C or Java.

Firstly, here is an example of how to use the struct named DecimalFloatingPoint to convert a float64/double to decimal floating-point:

void ExampleUsage()
{
	double inputNumber = 24.56789D;
	Console.WriteLine("Input Number = {0}", inputNumber.ToString());
	
	DecimalFloatingPoint decFp = new DecimalFloatingPoint(inputNumber, false);
	Console.WriteLine("Significand = {0}", decFp.Significand);
	Console.WriteLine("Exponent = {0}", decFp.Exponent);
	Console.WriteLine("IsNegativeSignificand = {0}", decFp.IsNegative);
	Console.WriteLine("IsInfinity = {0}", decFp.IsInfinity);
	Console.WriteLine("IsIndeterminate = {0}", decFp.IsIndeterminate);
}

The text written to the Console is:

Input Number = 24.56789
Significand = 2456789
Exponent = -5
IsNegativeSignificand = False
IsInfinity = False
IsIndeterminate = False

The source code for the struct DecimalFloatingPoint is:

// A decimal floating-point representation:  Significand * (10 ** Exponent)
public readonly struct DecimalFloatingPoint
{
	public readonly UInt64 Significand;
	public readonly Int16 Exponent;		// Exponent in the range -324 to +308 inclusive (when input is float64).
	public readonly bool IsNegative;	// Whether the number or significand is negative (not whether Exponent is negative).
	public readonly bool IsInfinity;	// If true, the number represents either positive infinity or negative infinity depending on IsNegative.
	public readonly bool IsIndeterminate;	// If true, the number represents IEEE NaN ("Not a Number").

	public DecimalFloatingPoint(UInt64 inSignificand, Int16 inExponent, bool inNegative, bool inInfinity, bool inIndeterminate)
	{
		this.Significand = inSignificand;
		this.Exponent = inExponent;
		this.IsNegative = inNegative;
		this.IsInfinity = inInfinity;
		this.IsIndeterminate = inIndeterminate;
	}

	// Converts inNumber (float64) from radix-2 floating-point to a decimal floating-point representation:  Significand * (10 ** Exponent)
	public DecimalFloatingPoint(double inNumber, bool inScientificNotation = false)
	{
		unchecked {
			uint64_t bits = (uint64_t)System.BitConverter.DoubleToInt64Bits(inNumber);
			// Alternatively:  uint64_t bits = System.Runtime.CompilerServices.Unsafe.As<double, uint64_t>(ref f);

			// Decode bits into sign, mantissa, and exponent.
			this.IsNegative = ((bits >> (DOUBLE_MANTISSA_BITS + DOUBLE_EXPONENT_BITS)) & 1) != 0;
			uint64_t ieeeMantissa = bits & ((1UL << DOUBLE_MANTISSA_BITS) - 1);
			uint32_t ieeeExponent = (uint32_t)((bits >> DOUBLE_MANTISSA_BITS) & ((1UL << DOUBLE_EXPONENT_BITS) - 1));

			// Check for special values.
			this.IsIndeterminate = false;
			this.IsInfinity = false;
			if (ieeeExponent == ((1UL << DOUBLE_EXPONENT_BITS) - 1UL) || (ieeeExponent == 0 && ieeeMantissa == 0))
			{
				if (ieeeMantissa != 0)
					this.IsIndeterminate = true;
				else if (ieeeExponent != 0)
					this.IsInfinity = true;
				// else inNumber is zero (or negative zero).
				this.Significand = 0;
				this.Exponent = 0;
			}
			else
			{
				floating_decimal_64 v;
				if (d2d_small_int(ieeeMantissa, ieeeExponent, out v))
				{
					if (inScientificNotation)
					{
						// For small integers in the range [1, 2^53), v.mantissa might contain trailing (decimal) zeros.
						// For scientific notation we need to move these zeros into the exponent.
						// (This is not needed for fixed-point notation, so it might be beneficial to trim trailing zeros in to_chars only if needed - once fixed-point notation output is implemented.)
						uint64_t v_mantissa = v.mantissa;
						int32_t v_exponent = v.exponent;
						for ( ; ; )
						{
							uint64_t q = div10(v_mantissa);
							uint32_t r = ((uint32_t)v_mantissa) - 10 * ((uint32_t)q);
							if (r != 0) break;
							v_mantissa = q;
							++v_exponent;
						}
						v = new floating_decimal_64(v_mantissa, v_exponent);
					}
				}
				else
				{
					v = d2d(ieeeMantissa, ieeeExponent);
				}
				this.Significand = v.mantissa;
				this.Exponent = unchecked((int16_t)v.exponent);
			}
		} // unchecked
	}

} // struct
@ulfjack
Copy link
Owner

ulfjack commented Dec 7, 2020

I'm happy to consider pull requests or link to wherever you intend to host your extensions. Posting C# code on this issue tracker doesn't seem super useful, though. I think the discussion of a decimal floating point struct came up before in #27.

@ulfjack ulfjack closed this as completed Dec 7, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants