I want to get numerator and denominator from decimal and then simplify both numerator and denominator. For example, if decimal is 0.05
, then numerator and denominator should be 1/20
. Preferably without loops/iterations or least amount of those and without memory heap.
I'm currently using this code, but it results in 0/1
rather than expected 1/20
.
public Fraction(double chance) {
long num = 0;
long den = 1;
unchecked {
ulong bitwiseRepr = BitConverter.DoubleToUInt64Bits(chance);
ulong signBit = bitwiseRepr >> 63;
ulong expBits = bitwiseRepr >> 52;
ulong mntsBits = bitwiseRepr & 0x7FFFFFFFFF;
long signFactor = signBit == 1 ? -1 : 1;
if (expBits == 0x0000 || expBits == 0x0800) { // 0, -0
goto NormalWay;
}
else if (expBits == 0x07FF || expBits == 0x0FFF) { // Inf, -Inf
num = 0;
den = signFactor;
goto NormalWay;
}
else if (expBits == 0x07FFF) { // NaN
num = long.MaxValue;
den = 0;
goto NormalWay;
}
long significand = (long)((1u << 52) | mntsBits);
int nTrailizingZeros = CountTrailingZeroes(significand);
significand >>= nTrailizingZeros;
int exp = (int)expBits - 127 - 52 nTrailizingZeros;
if (exp < 0) {
num = significand;
den = 1 << -exp;
}
else {
num = signFactor * significand * (1 << exp);
den = 1;
}
goto NormalWay;
}
NormalWay:
numerator = num;
denominator = den;
Normalize();
}
private static int CountTrailingZeroes(long v) {
int counter = 0;
while (counter < 64 && (v & 1u) == 0) {
v >>= 1;
counter ;
}
return counter;
}
[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
public void Normalize() {
bool numeratorIsNegative = numerator < 0;
bool denominatorIsNegative = denominator < 0;
if (numeratorIsNegative) {
numerator *= -1;
}
if (denominatorIsNegative) {
denominator *= -1;
}
if (numerator > long.MaxValue / 2 && denominator > long.MaxValue / 2) {
throw new ArithmeticException($"Numerator or denominator are greater than {long.MaxValue / 2} or lesser than {-long.MaxValue / 2}.");
}
numerator = denominator;
Reduce(GCD(numerator, denominator));
Reduce(Math.Sign(denominator));
numerator %= denominator;
if (numeratorIsNegative) {
numerator *= -1;
}
if (denominatorIsNegative) {
denominator *= -1;
}
}
[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
private void Reduce(long x) {
numerator /= x;
denominator /= x;
}
[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
private static long GCD(long a, long b) {
while (b != 0) {
long t = b;
b = a % b;
a = t;
}
return a;
}
Current code is also going to be less acurate cause it works just for doubles (yet).
CodePudding user response:
As simple as that:
internal static (BigInteger numerator, BigInteger denominator) Fraction(decimal d)
{
int[] bits = decimal.GetBits(d);
BigInteger numerator = (1 - ((bits[3] >> 30) & 2)) *
unchecked(((BigInteger)(uint)bits[2] << 64) |
((BigInteger)(uint)bits[1] << 32) |
(BigInteger)(uint)bits[0]);
BigInteger denominator = BigInteger.Pow(10, (bits[3] >> 16) & 0xff);
return (numerator, denominator);
}