转换为分数英寸。

4
我需要将一个双精度浮点数(厘米)转换为分数格式,例如:3 1/64英寸。虽然我找到了一些转换为分数的算法,但它们并不适用于我的需求,因为我的分数应该是这些格式:?/2、?/4、?/8、?/16、?/32、?/64。我看过转换表格,如此链接。我认为最好的解决方案是创建一个键值列表,其中包含表格中的所有值,并针对每个数字在列表中找到最佳逼近值。
例如:3.21厘米= 1.26378英寸= 1英寸+0.26378。因此,根据链接的表格,0.26378 = 17/64。最终结果应为1 17/64英寸。
所以我的问题是:
1.是否有一个包含表格中所有值的列表,并找到最接近值以给出分数是一个好主意?还是更好地为此创建一个算法? 2.如果创建一个包含值的列表是可行的,如何在列表中找到给定数字的最接近值?

表格的问题在于,如果您得到一个比表格所能表示的任何值都大的值怎么办?使用算法并没有问题 - 您会得到一个更准确的答案。如果您需要以某种格式向用户显示它,那么也会有一种数学方法来计算它应该看起来像什么。 - ADyson
不。你的问题可以简单地用二进制来定义。然后通过左移将二进制扩展转换为分数。 - Aron
4个回答

6

我建议使用简单的数学公式,而不是表格。

private static string ToFraction64(double value) {
  // denominator is fixed
  int denominator = 64;
  // integer part, can be signed: 1, 0, -3,...
  int integer = (int) value;
  // numerator: always unsigned (the sign belongs to the integer part)
  // + 0.5 - rounding, nearest one: 37.9 / 64 -> 38 / 64; 38.01 / 64 -> 38 / 64
  int numerator = (int) ((Math.Abs(value) - Math.Abs(integer)) * denominator + 0.5);

  // some fractions, e.g. 24 / 64 can be simplified:
  // both numerator and denominator can be divided by the same number
  // since 64 = 2 ** 6 we can try 2 powers only 
  // 24/64 -> 12/32 -> 6/16 -> 3/8
  // In general case (arbitrary denominator) use gcd (Greatest Common Divisor):
  //   double factor = gcd(denominator, numerator);
  //   denominator /= factor;
  //   numerator /= factor;
  while ((numerator % 2 == 0) && (denominator % 2 == 0)) {
    numerator /= 2;
    denominator /= 2;
  }

  // The longest part is formatting out

  // if we have an actual, not degenerated fraction (not, say, 4 0/1)
  if (denominator > 1)
    if (integer != 0) // all three: integer + numerator + denominator
      return string.Format("{0} {1}/{2}", integer, numerator, denominator);
    else if (value < 0) // negative numerator/denominator, e.g. -1/4
      return string.Format("-{0}/{1}", numerator, denominator);
    else // positive numerator/denominator, e.g. 3/8
      return string.Format("{0}/{1}", numerator, denominator);
  else 
    return integer.ToString(); // just an integer value, e.g. 0, -3, 12...  
}

测试

const double cmInInch = 2.54;

// 1 17/64
Console.Write(ToFraction64(3.21 / cmInInch));
// -1 17/64
Console.Write(ToFraction64(-1.26378));
// 3 1/4
Console.Write(ToFraction64(3.25001));
// 3 1/4
Console.Write(ToFraction64(3.24997));
// 5
Console.Write(ToFraction64(5.000001));
// -1/8
Console.Write(ToFraction64(-0.129));
// 1/8
Console.Write(ToFraction64(0.129));

问题缺少算法的解释。您是否从64开始迭代分母,一直到底部(32,16,...2),并检查误差以找到最佳匹配,还是只适用于精确值? - Sinatr
我现在明白了,感谢解释。所以你基本上是提取小数部分,将其乘以分母,向上舍入,然后简化:37.4 = 37 + 0.4,然后 0.4 = (0.4 * 64 + 0.5) / 64 = 26.1 / 64 ~ 26 / 64 = 13 / 32。 - Sinatr
这个解决方案似乎是有效的,但我想了解“int numerator = ...”这一行。你能否详细解释一下? - Acel
@Acel: (Math.Abs(value) - Math.Abs(integer) - 提取无符号小数部分(例如 -3.128789 -> 0.128789); * denominator- 小数部分以1/64 *单位* 表示 (0.128789 -> 8.2425../64); 最后,(int) (... + 0.5) - *四舍五入* (8.2425..->8`)。 - Dmitry Bychenko
@Acel: (int)不会四舍五入,而是直接舍去小数部分;这就是加上0.5的原因。 - Dmitry Bychenko
显示剩余2条评论

1

增加了“feet”的显示

public static string ToFraction(this double source, int denominator)
{
    var divider = denominator;
    var inches = (int) Math.Abs(source);
    var numerator = (int) ((Math.Abs(source) - Math.Abs(inches)) * divider + 0.5);

    while (numerator % 2 == 0 && divider % 2 == 0)
    {
        numerator /= 2;
        divider /= 2;
    }

    if (divider == numerator)
    {
        if (source < 0) inches--;
        else inches++;
        numerator = 0;
    }

    var feet = Math.DivRem(inches, 12, out inches);

    var valueBuilder = new StringBuilder();
    if (source + 1d / denominator < 0) valueBuilder.Insert(0, "-");

    if (feet > 0)
    {
        valueBuilder.Append(feet);
        valueBuilder.Append("'");
        valueBuilder.Append("-");
    }

    valueBuilder.Append(inches);

    if (numerator != 0)
    {
        valueBuilder.Append(" ");
        valueBuilder.Append(numerator);
        valueBuilder.Append("/");
        valueBuilder.Append(divider);
    }

    valueBuilder.Append('"');
    return valueBuilder.ToString();
}

所有测试通过
    [TestCase]
    public void FractionTest()
    {
        Assert.AreEqual("0\"", 0d.ToFraction());
        Assert.AreEqual("0\"", (-0d).ToFraction());
        Assert.AreEqual("0\"", (-0.00001d).ToFraction());
        Assert.AreEqual("1\"", 1d.ToFraction());
        Assert.AreEqual("-1\"", (-1d).ToFraction());
        Assert.AreEqual("0 1/8\"", 0.129.ToFraction());
        Assert.AreEqual("-0 1/8\"", (-0.129).ToFraction());
        Assert.AreEqual("-1 1/4\"", (-1.26378).ToFraction());
        Assert.AreEqual("5\"", 5.000001.ToFraction());
        Assert.AreEqual("3 1/4\"", 3.24997.ToFraction());
        Assert.AreEqual("3 1/4\"", 3.25001.ToFraction());
        Assert.AreEqual("1'-0\"", 12d.ToFraction());
        Assert.AreEqual("1'-0 3/32\"", 12.1d.ToFraction());
        Assert.AreEqual("1'-1\"", 13d.ToFraction());
        Assert.AreEqual("1'-3 1/8\"", 15.125d.ToFraction());
        Assert.AreEqual("1'-0\"", 12.00001d.ToFraction());
        Assert.AreEqual("-1'-0\"", (-12.00001d).ToFraction());
        Assert.AreEqual("-2'-1 7/32\"", (-25.231d).ToFraction());
    }

1
使用Dmitry Bychenko的代码为基础,我们需要添加一个新的条件判断"else if (denominator == numerator)"。如果分数等于1,那么当数值为正时添加1,当数值为负时减去1(例如,denominator/numerator = 64/64)。
    private static string ToFraction64(double value)
    {
        // denominator is fixed
        int denominator = 64;
        // integer part, can be signed: 1, 0, -3,...
        int integer = (int)value;
        // numerator: always unsigned (the sign belongs to the integer part)
        // + 0.5 - rounding, nearest one: 37.9 / 64 -> 38 / 64; 38.01 / 64 -> 38 / 64
        int numerator = (int)((Math.Abs(value) - Math.Abs(integer)) * denominator + 0.5);

        // some fractions, e.g. 24 / 64 can be simplified:
        // both numerator and denominator can be divided by the same number
        // since 64 = 2 ** 6 we can try 2 powers only 
        // 24/64 -> 12/32 -> 6/16 -> 3/8
        // In general case (arbitrary denominator) use gcd (Greatest Common Divisor):
        //   double factor = gcd(denominator, numerator);
        //   denominator /= factor;
        //   numerator /= factor;
        while ((numerator % 2 == 0) && (denominator % 2 == 0))
        {
            numerator /= 2;
            denominator /= 2;
        }

        // The longest part is formatting out

        // if we have an actual, not degenerated fraction (not, say, 4 0/1)
        if (denominator > 1)
            if (integer != 0) // all three: integer + numerator + denominator
                return string.Format("{0} {1}/{2}", integer, numerator, denominator);
            else if (value < 0) // negative numerator/denominator, e.g. -1/4
                return string.Format("-{0}/{1}", numerator, denominator);
            else // positive numerator/denominator, e.g. 3/8
                return string.Format("{0}/{1}", numerator, denominator);
        //if fraction equals to 1 we add 1 if the value is positive or remove 1 if the value is negative (ex denominator/numerator = 64/64)
        else if (denominator == numerator)
        {
            if (value < 0) // negative numerator/denominator, e.g. -1/4               
                integer--;
            else // positive numerator/denominator, e.g. 3/8               
                integer++;
            return integer.ToString();
        }
        else
            return integer.ToString(); // just an integer value, e.g. 0, -3, 12...  
    }

0

我的函数更简单易用。它可以从一个十进制数值返回一个由三个整数组成的数组 {Inches, Numerator, Denominator},其中fracBase参数表示精度为16、32、64、128等。

public static int[] GetImpFractions(decimal value, int fracBase = 32)
{
    int[] result = { 0, 0, 0 };

    result[0] = (int)Math.Truncate(value);
    decimal num = (value - (decimal)result[0]);
    num *= fracBase;
    decimal denom = fracBase;
    if (num > 0)
    {
        while (num % 2 == 0) 
        {
            num /= 2;
            denom /= 2;
        }
        if (num == 1 && denom == 1)
        {
            result[0] += 1;
            num = 0; 
            denom = 0;
        }
        result[1] = (int)Math.Truncate(num);
        result[2] = (int)Math.Truncate(denom);
    }

    return result;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接