// // This method is copied directly from CoreRT (which is in turn a C#-ized version of the CoreCLR C++ code.) // public static void RoundNumber(ref NumberBuffer number, int pos) { number.CheckConsistency(); Span <byte> digits = number.Digits; int i = 0; while (i < pos && digits[i] != 0) { i++; } if (i == pos && digits[i] >= (byte)'5') { while (i > 0 && digits[i - 1] == (byte)'9') { i--; } if (i > 0) { digits[i - 1]++; } else { number.Scale++; digits[0] = (byte)'1'; i = 1; } } else { while (i > 0 && digits[i - 1] == (byte)'0') { i--; } } if (i == 0) { number.Scale = 0; if (number.Kind == NumberBufferKind.Integer) { number.IsNegative = false; } } digits[i] = 0; number.CheckConsistency(); }
/// <summary> /// Formats a Decimal as a UTF8 string. /// </summary> /// <param name="value">Value to format</param> /// <param name="buffer">Buffer to write the UTF8-formatted value to</param> /// <param name="bytesWritten">Receives the length of the formatted text in bytes</param> /// <param name="format">The standard format to use</param> /// <returns> /// true for success. "bytesWritten" contains the length of the formatted text in bytes. /// false if buffer was too short. Iteratively increase the size of the buffer and retry until it succeeds. /// </returns> /// <remarks> /// Formats supported: /// G/g (default) /// F/f 12.45 Fixed point /// E/e 1.245000e1 Exponential /// </remarks> /// <exceptions> /// <cref>System.FormatException</cref> if the format is not valid for this data type. /// </exceptions> public static bool TryFormat(decimal value, Span <byte> buffer, out int bytesWritten, StandardFormat format = default) { if (format.IsDefault) { format = 'G'; } switch (format.Symbol) { case 'g': case 'G': { if (format.Precision != StandardFormat.NoPrecision) { throw new NotSupportedException(SR.Argument_GWithPrecisionNotSupported); } NumberBuffer number = default; Number.DecimalToNumber(value, ref number); if (number.Digits[0] == 0) { number.IsNegative = false; // For Decimals, -0 must print as normal 0. } bool success = TryFormatDecimalG(ref number, buffer, out bytesWritten); #if DEBUG // This DEBUG segment exists to close a code coverage hole inside TryFormatDecimalG(). Because we don't call RoundNumber() on this path, we have no way to feed // TryFormatDecimalG() a number where trailing zeros before the decimal point have been cropped. So if the chance comes up, we'll crop the zeroes // ourselves and make a second call to ensure we get the same outcome. if (success) { Span <byte> digits = number.Digits; int numDigits = number.NumDigits; if (numDigits != 0 && number.Scale == numDigits && digits[numDigits - 1] == '0') { while (numDigits != 0 && digits[numDigits - 1] == '0') { digits[numDigits - 1] = 0; numDigits--; } number.CheckConsistency(); byte[] buffer2 = new byte[buffer.Length]; bool success2 = TryFormatDecimalG(ref number, buffer2, out int bytesWritten2); Debug.Assert(success2); Debug.Assert(bytesWritten2 == bytesWritten); for (int i = 0; i < bytesWritten; i++) { Debug.Assert(buffer[i] == buffer2[i]); } } } #endif // DEBUG return(success); } case 'f': case 'F': { NumberBuffer number = default; Number.DecimalToNumber(value, ref number); byte precision = (format.Precision == StandardFormat.NoPrecision) ? (byte)2 : format.Precision; Number.RoundNumber(ref number, number.Scale + precision); Debug.Assert(!(number.Digits[0] == 0 && number.IsNegative)); // For Decimals, -0 must print as normal 0. As it happens, Number.RoundNumber already ensures this invariant. return(TryFormatDecimalF(ref number, buffer, out bytesWritten, precision)); } case 'e': case 'E': { NumberBuffer number = default; Number.DecimalToNumber(value, ref number); byte precision = (format.Precision == StandardFormat.NoPrecision) ? (byte)6 : format.Precision; Number.RoundNumber(ref number, precision + 1); Debug.Assert(!(number.Digits[0] == 0 && number.IsNegative)); // For Decimals, -0 must print as normal 0. As it happens, Number.RoundNumber already ensures this invariant. return(TryFormatDecimalE(ref number, buffer, out bytesWritten, precision, exponentSymbol: (byte)format.Symbol)); } default: return(ThrowHelper.TryFormatThrowFormatException(out bytesWritten)); } }
private static bool TryParseNumber(ReadOnlySpan <byte> text, ref NumberBuffer number, out int bytesConsumed, ParseNumberOptions options, out bool textUsedExponentNotation) { Debug.Assert(number.Digits[0] == 0 && number.Scale == 0 && !number.IsNegative, "Number not initialized to default(NumberBuffer)"); textUsedExponentNotation = false; if (text.Length == 0) { bytesConsumed = 0; return(false); } Span <byte> digits = number.Digits; int srcIndex = 0; int dstIndex = 0; // Consume the leading sign if any. byte c = text[srcIndex]; switch (c) { case Utf8Constants.Minus: number.IsNegative = true; goto case Utf8Constants.Plus; case Utf8Constants.Plus: srcIndex++; if (srcIndex == text.Length) { bytesConsumed = 0; return(false); } c = text[srcIndex]; break; default: break; } int startIndexDigitsBeforeDecimal = srcIndex; // Throw away any leading zeroes while (srcIndex != text.Length) { c = text[srcIndex]; if (c != '0') { break; } srcIndex++; } if (srcIndex == text.Length) { digits[0] = 0; number.Scale = 0; bytesConsumed = srcIndex; number.CheckConsistency(); return(true); } int startIndexNonLeadingDigitsBeforeDecimal = srcIndex; while (srcIndex != text.Length) { c = text[srcIndex]; if ((c - 48u) > 9) { break; } srcIndex++; } int numDigitsBeforeDecimal = srcIndex - startIndexDigitsBeforeDecimal; int numNonLeadingDigitsBeforeDecimal = srcIndex - startIndexNonLeadingDigitsBeforeDecimal; Debug.Assert(dstIndex == 0); int numNonLeadingDigitsBeforeDecimalToCopy = Math.Min(numNonLeadingDigitsBeforeDecimal, NumberBuffer.BufferSize - 1); text.Slice(startIndexNonLeadingDigitsBeforeDecimal, numNonLeadingDigitsBeforeDecimalToCopy).CopyTo(digits); dstIndex = numNonLeadingDigitsBeforeDecimalToCopy; number.Scale = numNonLeadingDigitsBeforeDecimal; if (srcIndex == text.Length) { bytesConsumed = srcIndex; number.CheckConsistency(); return(true); } int numDigitsAfterDecimal = 0; if (c == Utf8Constants.Period) { // // Parse the digits after the decimal point. // srcIndex++; int startIndexDigitsAfterDecimal = srcIndex; while (srcIndex != text.Length) { c = text[srcIndex]; if ((c - 48u) > 9) { break; } srcIndex++; } numDigitsAfterDecimal = srcIndex - startIndexDigitsAfterDecimal; int startIndexOfDigitsAfterDecimalToCopy = startIndexDigitsAfterDecimal; if (dstIndex == 0) { // Not copied any digits to the Number struct yet. This means we must continue discarding leading zeroes even though they appeared after the decimal point. while (startIndexOfDigitsAfterDecimalToCopy < srcIndex && text[startIndexOfDigitsAfterDecimalToCopy] == '0') { number.Scale--; startIndexOfDigitsAfterDecimalToCopy++; } } int numDigitsAfterDecimalToCopy = Math.Min(srcIndex - startIndexOfDigitsAfterDecimalToCopy, NumberBuffer.BufferSize - dstIndex - 1); text.Slice(startIndexOfDigitsAfterDecimalToCopy, numDigitsAfterDecimalToCopy).CopyTo(digits.Slice(dstIndex)); dstIndex += numDigitsAfterDecimalToCopy; // We "should" really NUL terminate, but there are multiple places we'd have to do this and it is a precondition that the caller pass in a fully zero=initialized Number. if (srcIndex == text.Length) { if (numDigitsBeforeDecimal == 0 && numDigitsAfterDecimal == 0) { // For compatibility. You can say "5." and ".5" but you can't say "." bytesConsumed = 0; return(false); } bytesConsumed = srcIndex; number.CheckConsistency(); return(true); } } if (numDigitsBeforeDecimal == 0 && numDigitsAfterDecimal == 0) { bytesConsumed = 0; return(false); } if ((c & ~0x20u) != 'E') { bytesConsumed = srcIndex; number.CheckConsistency(); return(true); } // // Parse the exponent after the "E" // textUsedExponentNotation = true; srcIndex++; if ((options & ParseNumberOptions.AllowExponent) == 0) { bytesConsumed = 0; return(false); } if (srcIndex == text.Length) { bytesConsumed = 0; return(false); } bool exponentIsNegative = false; c = text[srcIndex]; switch (c) { case Utf8Constants.Minus: exponentIsNegative = true; goto case Utf8Constants.Plus; case Utf8Constants.Plus: srcIndex++; if (srcIndex == text.Length) { bytesConsumed = 0; return(false); } c = text[srcIndex]; break; default: break; } if (!Utf8Parser.TryParseUInt32D(text.Slice(srcIndex), out uint absoluteExponent, out int bytesConsumedByExponent)) { bytesConsumed = 0; return(false); } srcIndex += bytesConsumedByExponent; if (exponentIsNegative) { if (number.Scale < int.MinValue + (long)absoluteExponent) { // A scale underflow means all non-zero digits are all so far to the right of the decimal point, no // number format we have will be able to see them. Just pin the scale at the absolute minimum // and let the converter produce a 0 with the max precision available for that type. number.Scale = int.MinValue; } else { number.Scale -= (int)absoluteExponent; } } else { if (number.Scale > int.MaxValue - (long)absoluteExponent) { bytesConsumed = 0; return(false); } number.Scale += (int)absoluteExponent; } bytesConsumed = srcIndex; number.CheckConsistency(); return(true); }