|
// 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.
using System;
using System.Diagnostics;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp
{
using static BinaryOperatorKind;
internal static partial class ValueSetFactory
{
private class DecimalTC : INumericTC<decimal>
{
public static readonly DecimalTC Instance = new DecimalTC();
// These are the smallest nonzero normal mantissa value (in three parts) below which you could use a higher scale.
// This is the 96-bit representation of ((2^96)-1) / 10;
private const uint transitionLow = 0x99999999;
private const uint transitionMid = 0x99999999;
private const uint transitionHigh = 0x19999999;
private const byte maxScale = 28;
private static readonly decimal normalZero = new decimal(lo: 0, mid: 0, hi: 0, isNegative: false, scale: maxScale);
private static readonly decimal epsilon = new decimal(lo: 1, mid: 0, hi: 0, isNegative: false, scale: maxScale);
decimal INumericTC<decimal>.MinValue => decimal.MinValue;
decimal INumericTC<decimal>.MaxValue => decimal.MaxValue;
decimal INumericTC<decimal>.Zero => 0M;
public decimal FromConstantValue(ConstantValue constantValue) => constantValue.IsBad ? 0m : constantValue.DecimalValue;
public ConstantValue ToConstantValue(decimal value) => ConstantValue.Create(value);
public decimal Next(decimal value)
{
Debug.Assert(value != decimal.MaxValue);
if (value == 0m)
return epsilon;
var (low, mid, high, isNegative, scale) = DecimalRep.FromValue(value);
Debug.Assert(scale == DecimalRep.FromValue(value).Normalize().scale); // assert that the input is normalized
if (isNegative)
{
// get the next value closer to zero.(less negative)
if (value == -epsilon)
return normalZero; // skip negative zero
// This should not occur, as numbers such as this are not in our normal form (not at maximum scale).
Debug.Assert(!(scale < 28 && low == transitionLow && mid == transitionMid && high == transitionHigh));
if (low != 0)
return new DecimalRep(low: low - 1, mid: mid, high: high, isNegative: isNegative, scale: scale).Value;
if (mid != 0)
return new DecimalRep(low: uint.MaxValue, mid: mid - 1, high: high, isNegative: isNegative, scale: scale).Value;
Debug.Assert(high > 0); // otherwise value == 0m
return new DecimalRep(low: uint.MaxValue, mid: uint.MaxValue, high: high - 1, isNegative: isNegative, scale: scale).Value;
}
else
{
// get the next value farther from zero.(more positive)
if (low != uint.MaxValue)
return new DecimalRep(low: low + 1, mid: mid, high: high, isNegative: isNegative, scale: scale).Value;
if (mid != uint.MaxValue)
return new DecimalRep(low: 0, mid: mid + 1, high: high, isNegative: isNegative, scale: scale).Value;
if (high != uint.MaxValue)
return new DecimalRep(low: 0, mid: 0, high: high + 1, isNegative: isNegative, scale: scale).Value;
// the mantissa it at its maximum value. Divide the mantissa by 10 and decrease the scale.
// Since we know the value of the mantissa, we can simply assign mantissa/10 here.
low = transitionLow;
mid = transitionMid;
high = transitionHigh;
Debug.Assert(scale > 0); // otherwise value == decimal.MaxValue
scale -= 1;
var result = new DecimalRep(low: low + 1, mid: mid, high: high, isNegative: isNegative, scale: scale).Value;
// Assert that the value returned really is the next possible value.
Debug.Assert(new DecimalRep(low: low, mid: mid, high: high, isNegative: isNegative, scale: scale).Value <= value);
Debug.Assert(result > value);
return result;
}
}
bool INumericTC<decimal>.Related(BinaryOperatorKind relation, decimal left, decimal right)
{
switch (relation)
{
case Equal:
return left == right;
case GreaterThanOrEqual:
return left >= right;
case GreaterThan:
return left > right;
case LessThanOrEqual:
return left <= right;
case LessThan:
return left < right;
default:
throw new ArgumentException("relation");
}
}
string INumericTC<decimal>.ToString(decimal value) => FormattableString.Invariant($"{value:G}");
decimal INumericTC<decimal>.Prev(decimal value)
{
return -Next(-value);
}
public decimal Random(Random random)
{
INumericTC<uint> uinttc = UIntTC.Instance;
return new DecimalRep(
low: uinttc.Random(random),
mid: uinttc.Random(random),
high: uinttc.Random(random),
isNegative: random.NextDouble() < 0.5,
scale: (byte)random.Next(0, maxScale + 1)).Normalize().Value;
}
public static decimal Normalize(decimal value) => DecimalRep.FromValue(value).Normalize().Value;
private readonly struct DecimalRep
{
public readonly uint low;
public readonly uint mid;
public readonly uint high;
public readonly bool isNegative;
public readonly byte scale;
public DecimalRep(uint low, uint mid, uint high, bool isNegative, byte scale)
{
if (scale > maxScale)
throw new ArgumentException("scale");
this.low = low;
this.mid = mid;
this.high = high;
this.isNegative = isNegative;
this.scale = scale;
}
public decimal Value => new decimal(lo: (int)low, mid: (int)mid, hi: (int)high, isNegative: isNegative, scale: scale);
public DecimalRep Normalize()
{
// return the number in the highest possible scale (containing the most precision)
if (this.scale == maxScale)
return this;
var (low, mid, high, isNegative, scale) = this;
while (scale < maxScale)
{
if (high * 10L > uint.MaxValue)
break;
long newHigh = 10L * high;
long newMid = 10L * mid;
long newLow = 10L * low;
newMid += newLow >> 32;
newLow &= uint.MaxValue;
newHigh += newMid >> 32;
newMid &= uint.MaxValue;
if (newHigh > uint.MaxValue)
break;
low = (uint)newLow;
mid = (uint)newMid;
high = (uint)newHigh;
scale += 1;
}
return new DecimalRep(low, mid, high, isNegative, scale);
}
public static DecimalRep FromValue(decimal value)
{
value.GetBits(out bool isNegative, out byte scale, out uint low, out uint mid, out uint high);
Debug.Assert(scale <= maxScale);
return new DecimalRep(low: low, mid: mid, high: high, isNegative: isNegative, scale: scale);
}
public void Deconstruct(out uint low, out uint mid, out uint high, out bool isNegative, out byte scale) =>
(low, mid, high, isNegative, scale) = (this.low, this.mid, this.high, this.isNegative, this.scale);
public override string ToString() => $"Decimal({(isNegative ? "-" : "+")}, 0x{high:08X} 0x{mid:08X} 0x{low:08X} *10^-{scale})";
}
}
}
}
|