postgresql 如何在C#中表示像1/3这样的分数?

nwnhqdif  于 2022-12-29  发布在  PostgreSQL
关注(0)|答案(4)|浏览(431)

我正在写一个菜谱应用,需要对食材进行加减运算,才能制作出一个主购物清单。但我不知道如何表示1/3这样的分数(不会转换成干净的小数),不会得到.9999999......而不是1。我甚至不能使用盎司作为基本单位,因为一杯的三分之一是2。666666盎司
简而言之,这个想法是,如果用户正在制作使用糖的食谱,他们可能会有一个要求一杯,另一个要求半杯,另一个要求三分之一杯,这将告诉他们,他们需要在下次购物时购买1 5/6杯糖。
有没有人能解决这个问题,或者我应该只是禁止所有有丑陋分数的食谱(这远不理想)?
到目前为止,我想到的最接近的方法是将每种成分存储为三分之一盎司的倍数,但这会使后端逻辑有点复杂!
加分,如果修复将允许我很容易地存储在一个Postgres数据库的价值!

jw5wzhpr

jw5wzhpr1#

我认为最好将一个分数的现有实现结合起来,比如Fractions NuGet package中的Fraction类型和一个单位,这将是一个枚举。

struct Amount
{
    public Amount(Fraction quantity, Unit unit)
    {
        Quantity = quantity;
        Unit = unit;
    }

    public Fraction Quantity { get; }
    public Unit Unit { get; }

    ...
}

public enum Unit { Ounce, Cup, Gram, ... };

这就给你留下了不同单位之间的转换,如杯/盎司,但我会添加一些关于四舍五入的规则,这样你仍然可以使用合理的分数。因为你谈论的是食谱,这似乎是一个合理的约束,因为如果你偏离了一个小的数量,它不会对食谱有太大的影响(希望如此)!
转换将隐藏在运算符后面;你会重载+=等等。
对于存储,您需要3列:分子、分母和单位。这使得从DB到C#的Map为1:1。
你甚至可以像@Cleptus在评论中建议的那样做一个user defined data type,这样可以保持1:1的Map,但是这种方法有一个缺点,正如@Jeremy在评论中所说的:缺乏对聚合的支持,比如平均值。如果你不需要担心这个(例如,相关食谱的平均糖用量),那么这不是一个问题。
是的,您可以自己做所有的计算,甚至封装它,但我认为自然表示更易于维护,并且可以很好地从客户机Map到服务器,再Map到存储。
最后,因为涉及到舍入,所以应该尽可能在最后一刻进行,以减少误差累积,特别是在执行求和、求平均值等聚合函数时。

knsnq2tg

knsnq2tg2#

硬币有两个方面。首先,存储精确的值不是一个bug,而是一个特性。尽管用某些数字除会产生难看的结果,但在存储时不必担心它们。然而,您确实需要解决硬币的另一个方面,即显示。如果需要,您可以使用带有几位小数的Math.round。当我们谈到菜谱时,需要一定程度的精确性。但是如果有比绝对精确值更多的NaCl分子进入你的汤中,你的汤不会被破坏,所以为了这个目的,四舍五入似乎是一个合理的方法。

nwlqm0z1

nwlqm0z13#

假设你正在处理“简单的”* 普通分数 *(你可以面对,比如1/31 5/63/4,但不能面对103/2043),你可以尝试乘以某个好的 * 公因子 *。60600420是典型的选择:

1/3 * 60   ==  20
   1 5/6 * 60 == 110
   3/4 * 60   ==  45

所以你有很好的旧整数值,很容易存储和操作。当我们想把结果表示为普通分数时,我们只需要计算GreatestCommonDivisor gcd(result, factor)。例如:
所以我们存储95。当我们想要表示它时,因为gcd(95, 60) = 5

95 / 60 == 19 / 12 == 1 7/12

在 * 一般情况 * 下,当任何普通分数在技术上都是可能的时,我们必须使用BigRational或类似结构,例如https://www.nuget.org/packages?q=BigRational,并存储分子和分母

oxf4rvwz

oxf4rvwz4#

MS网站上有一个解决方案。给你一个结构体的示例,它存储2个值,分母和分母,并重载运算符以获得新的断裂示例。
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/operator-overloading

public readonly struct Fraction
{
    private readonly int num;
    private readonly int den;

    public Fraction(int numerator, int denominator)
    {
        if (denominator == 0)
        {
            throw new ArgumentException("Denominator cannot be zero.", nameof(denominator));
        }
        num = numerator;
        den = denominator;
    }

    public static Fraction operator +(Fraction a) => a;
    public static Fraction operator -(Fraction a) => new Fraction(-a.num, a.den);

    public static Fraction operator +(Fraction a, Fraction b)
        => new Fraction(a.num * b.den + b.num * a.den, a.den * b.den);

    public static Fraction operator -(Fraction a, Fraction b)
        => a + (-b);

    public static Fraction operator *(Fraction a, Fraction b)
        => new Fraction(a.num * b.num, a.den * b.den);

    public static Fraction operator /(Fraction a, Fraction b)
    {
        if (b.num == 0)
        {
            throw new DivideByZeroException();
        }
        return new Fraction(a.num * b.den, a.den * b.num);
    }

    public override string ToString() => $"{num} / {den}";
}

public static class OperatorOverloading
{
    public static void Main()
    {
        var a = new Fraction(5, 4);
        var b = new Fraction(1, 2);
        Console.WriteLine(-a);   // output: -5 / 4
        Console.WriteLine(a + b);  // output: 14 / 8
        Console.WriteLine(a - b);  // output: 6 / 8
        Console.WriteLine(a * b);  // output: 5 / 8
        Console.WriteLine(a / b);  // output: 10 / 4
    }
}

相关问题