You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何高效提取并化简小数的分子与分母?

问题修复与优化方案

核心问题分析

你提供的代码处理0.05得到0/1,主要由三个致命错误导致:

  • 指数位计算错误:未对双精度浮点数的11位指数位做掩码处理,导致指数值完全失真,最终分子溢出为0。
  • Normalize方法逻辑混乱:错误的numerator += denominator;和numerator %= denominator;直接破坏分子原始值,将其转为余数。
  • 亚正规数处理缺失:未区分正规数与亚正规数的尾数结构,亚正规数尾数无隐含最高位1,直接拼接会导致数值错误。

此外,仅支持double类型会因二进制浮点数的局限性导致精度损失,比如0.05无法用double精确存储,只能得到近似值。

修复后的完整代码

using System.Numerics; // .NET Core 3.0+ 支持BitOperations,旧框架可替换为自定义方法

public class Fraction
{
    public long Numerator { get; private set; }
    public long Denominator { get; private set; }

    public Fraction(double chance)
    {
        long num = 0;
        long den = 1;

        unchecked
        {
            ulong bitwiseRepr = BitConverter.DoubleToUInt64Bits(chance);
            ulong signBit = bitwiseRepr >> 63;
            ulong expBits = (bitwiseRepr >> 52) & 0x7FF; // 掩码提取11位指数位
            ulong mntsBits = bitwiseRepr & 0x000FFFFFFFFFFFFF; // 精确获取低52位尾数

            long signFactor = signBit == 1 ? -1 : 1;

            if (expBits == 0x7FF) // 处理无穷大与NaN
            {
                num = mntsBits != 0 ? long.MaxValue : 0;
                den = mntsBits != 0 ? 0 : signFactor;
                goto NormalWay;
            }

            ulong significandUInt;
            int expOffset;

            if (expBits == 0) // 处理亚正规数
            {
                significandUInt = mntsBits;
                expOffset = -126 - 52; // 亚正规数指数固定为-126,尾数无隐含1
            }
            else // 处理正规数
            {
                significandUInt = (1UL << 52) | mntsBits;
                expOffset = (int)expBits - 127 - 52;
            }

            if (significandUInt == 0) // 输入为0的情况
            {
                goto NormalWay;
            }

            // 无循环计算尾数 trailing zeros
            int nTrailingZeros = BitOperations.TrailingZeroCount(significandUInt);
            significandUInt >>= nTrailingZeros;
            expOffset += nTrailingZeros;

            long significand = (long)significandUInt;

            if (expOffset < 0)
            {
                num = signFactor * significand;
                den = 1L << -expOffset;
            }
            else
            {
                if (expOffset >= 63)
                    throw new OverflowException("数值过大,无法转换为long类型分数");
                num = signFactor * significand * (1L << expOffset);
                den = 1;
            }
        }

    NormalWay:
        Numerator = num;
        Denominator = den;
        Normalize();
    }

    // 支持decimal类型,解决十进制小数精度问题
    public Fraction(decimal value)
    {
        int[] bits = decimal.GetBits(value);
        long sign = bits[3] < 0 ? -1 : 1;
        int exponent = (bits[3] >> 16) & 0x1F;
        ulong mantissa = ((ulong)bits[2] << 64) | ((ulong)bits[1] << 32) | (uint)bits[0];

        if (mantissa > long.MaxValue)
            throw new OverflowException("decimal尾数过大,无法转换为long类型分数");

        Numerator = (long)mantissa * sign;
        Denominator = (long)Math.Pow(10, exponent);
        Normalize();
    }

    [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
    private void Normalize()
    {
        // 将负号移至分子,确保分母为正
        if (Denominator < 0)
        {
            Numerator *= -1;
            Denominator *= -1;
        }

        if (Numerator == 0)
        {
            Denominator = 1;
            return;
        }

        // 计算最大公约数约分
        long absNum = Math.Abs(Numerator);
        long gcd = GCD(absNum, Denominator);

        if (gcd > 1)
        {
            Numerator /= gcd;
            Denominator /= gcd;
        }
    }

    [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;
    }

    // 兼容旧.NET框架的trailing zeros计算(替代BitOperations)
    private static int CountTrailingZeroes(ulong v)
    {
        if (v == 0) return 64;
        int counter = 0;
        while ((v & 1) == 0)
        {
            v >>= 1;
            counter++;
        }
        return counter;
    }

    public override string ToString() => $"{Numerator}/{Denominator}";
}

关键优化点说明

  1. 修复指数位计算:通过& 0x7FF掩码提取正确的11位指数位,确保指数值准确。
  2. 重构Normalize方法:移除错误逻辑,仅保留符号处理与最大公约数约分,保证分数化简正确。
  3. 支持亚正规数:区分正规数与亚正规数的尾数结构,避免数值错误。
  4. 无循环优化:使用BitOperations.TrailingZeroCount替代循环计算 trailing zeros(旧框架可替换为CountTrailingZeroes方法)。
  5. 添加decimal支持:decimal是十进制浮点数,可精确表示0.05这类十进制小数,解决double的精度损失问题。

测试验证

  • 使用new Fraction(0.05d):因double的0.05是近似值,会得到与1/20等价的分数(如3602879701896397/72057594037927940)。
  • 使用new Fraction(0.05m):decimal的0.05是精确值,会直接得到1/20。

内容的提问来源于stack exchange,提问作者BasicallyIAmFox

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 23:05:25