【问题标题】:c# creating a custom "double" typec# 创建一个自定义的“double”类型
【发布时间】:2016-11-07 00:19:37
【问题描述】:

在我的应用程序中,我希望我所有存储金额的属性都舍入到 n 小数位。

为了代码清晰,我宁愿有一个自定义类型MoneyAmount,我的所有相应字段都会有,而不是必须在所有属性获取器/设置器中放置一个“Math.Round(value, n)”。

有没有一种巧妙的方法来实现这一点?

我看到 this post 关于重载赋值运算符 - 这是建议的方法吗?

编辑: 鉴于多个视图,我在这里发布我导出的完整代码:

public struct MoneyAmount {
const int N = 4;
private readonly double _value;

public MoneyAmount(double value) {
  _value = Math.Round(value, N);
}

#region mathematical operators
public static MoneyAmount operator +(MoneyAmount d1, MoneyAmount d2) {
  return new MoneyAmount(d1._value + d2._value);
}

public static MoneyAmount operator -(MoneyAmount d1, MoneyAmount d2) {
  return new MoneyAmount(d1._value - d2._value);
}

public static MoneyAmount operator *(MoneyAmount d1, MoneyAmount d2) {
  return new MoneyAmount(d1._value * d2._value);
}

public static MoneyAmount operator /(MoneyAmount d1, MoneyAmount d2) {
  return new MoneyAmount(d1._value / d2._value);
}
#endregion

#region logical operators
public static bool operator ==(MoneyAmount d1, MoneyAmount d2) {
  return d1._value == d2._value;
}
public static bool operator !=(MoneyAmount d1, MoneyAmount d2) {
  return d1._value != d2._value;
}
public static bool operator >(MoneyAmount d1, MoneyAmount d2) {
  return d1._value > d2._value;
}
public static bool operator >=(MoneyAmount d1, MoneyAmount d2) {
  return d1._value >= d2._value;
}
public static bool operator <(MoneyAmount d1, MoneyAmount d2) {
  return d1._value < d2._value;
}
public static bool operator <=(MoneyAmount d1, MoneyAmount d2) {
  return d1._value <= d2._value;
}
#endregion

#region Implicit conversions
/// <summary>
/// Implicit conversion from int to MoneyAmount. 
/// Implicit: No cast operator is required.
/// </summary>
public static implicit operator MoneyAmount(int value) {
  return new MoneyAmount(value);
}

/// <summary>
/// Implicit conversion from float to MoneyAmount. 
/// Implicit: No cast operator is required.
/// </summary>
public static implicit operator MoneyAmount(float value) {
  return new MoneyAmount(value);
}

/// <summary>
/// Implicit conversion from double to MoneyAmount. 
/// Implicit: No cast operator is required.
/// </summary>
public static implicit operator MoneyAmount(double value) {
  return new MoneyAmount(value);
}

/// <summary>
/// Implicit conversion from decimal to MoneyAmount. 
/// Implicit: No cast operator is required.
/// </summary>
public static implicit operator MoneyAmount(decimal value) {
  return new MoneyAmount(Convert.ToDouble(value));
}
#endregion

#region Explicit conversions
/// <summary>
/// Explicit conversion from MoneyAmount to int. 
/// Explicit: A cast operator is required.
/// </summary>
public static explicit operator int(MoneyAmount value) {
  return (int)value._value;
}

/// <summary>
/// Explicit conversion from MoneyAmount to float. 
/// Explicit: A cast operator is required.
/// </summary>
public static explicit operator float(MoneyAmount value) {
  return (float)value._value;
}

/// <summary>
/// Explicit conversion from MoneyAmount to double. 
/// Explicit: A cast operator is required.
/// </summary>
public static explicit operator double(MoneyAmount value) {
  return (double)value._value;
}

/// <summary>
/// Explicit conversion from MoneyAmount to decimal. 
/// Explicit: A cast operator is required.
/// </summary>
public static explicit operator decimal(MoneyAmount value) {
  return Convert.ToDecimal(value._value);
}
#endregion
}

【问题讨论】:

  • 旁注:使用 money 时,decimal 是更好的包装类型
  • 存储时四舍五入似乎很可疑。通常你只会在显示时四舍五入。
  • @MatthewWatson 不,它不是,实际上每种货币的小数位数是严格定义的。 OP实际上是在询问Money Pattern
  • @PanagiotisKanavos 如果您要四舍五入到最接近的货币单位,它会变得比仅存储 N 个小数位更复杂。例如,如果您想将金额拆分为 30%/70%,并且计算不会产生两个精确到 N 位小数的值,则必须确保在四舍五入后,这两个拆分值仍然相加正是原始值。除了简单的四舍五入到 N 位之外,这还需要额外的工作。
  • @MatthewWatson 我知道,这就是为什么Money 是一种模式。事实上,它甚至复杂,因为有一些规则可以处理最小化舍入误差(银行家的舍入算法只是一种技术)和分配丢失的美分,以便什么都不做迷路。其他管理货币转换的规则,其他管理允许每种货币的小数位数以及内部计算的小数位数

标签: c# types double rounding


【解决方案1】:

我建议如下:

  1. 创建一个名为 MoneyAmount 的新结构。
  2. 它包含一个字段:A double
  3. 带有一个double参数的构造函数,该构造函数将值四舍五入并赋值给内部字段。
  4. 将您可能需要的成员/运算符添加到您的结构中,使其具有与double 相同的所有操作,例如 +、- 等。但也可以从/转换到其他类型。每个操作都会生成一个具有舍入值的新 MoneyAmount 实例。
  5. 还可以考虑实现接口IFormattableIComparableIConvertible

简短示例:

public struct MoneyAmount
{
    const int N = 4;
    private readonly double _value;

    public MoneyAmount(double value)
    {
        _value = Math.Round(value, N);
    }

    // Example of one member of double:
    public static MoneyAmount operator *(MoneyAmount d1, MoneyAmount d2) 
    {
        return new MoneyAmount(d1._value * d2._value);
    }

    /// <summary>
    /// Implicit conversion from double to MoneyAmount. 
    /// Implicit: No cast operator is required.
    /// </summary>
    public static implicit operator MoneyAmount(double value)
    {
        return new MoneyAmount(value);
    }

    /// <summary>
    /// Explicit conversion from MoneyAmount to double. 
    /// Explicit: A cast operator is required.
    /// </summary>
    public static explicit operator double(MoneyAmount value)
    {
        return value._value;
    }

    /// <summary>
    /// Explicit conversion from MoneyAmount to int. 
    /// Explicit: A cast operator is required.
    /// </summary>
    public static explicit operator MoneyAmount(int value)
    {
        return new MoneyAmount(value);
    }

    /// <summary>
    /// Explicit conversion from MoneyAmount to int. 
    /// Explicit: A cast operator is required.
    /// </summary>
    public static explicit operator int(MoneyAmount value)
    {
        return (int)value._value;
    }

    // All other members here...
}

我意识到:double 有很多成员...

使用这些运算符,以下代码是可能的:

MoneyAmount m = 1.50; // Assignment from a double.
MoneyAmount n = 10; // Assignment from an integer.
m += n; // Mathematical operation with another MoneyAmount .
m *= 10; // Mathematical operation with an integer.
m -= 12.50; // Mathematical operation with a double.

编辑

您可能想要实现的所有转换方法:

  • 显式 MoneyAmount --> int
  • 显式 MoneyAmount --> 浮动
  • 显式 MoneyAmount --> double
  • 显式 MoneyAmount --> 十进制

  • 隐式 int--> MoneyAmount

  • 隐式浮动 --> MoneyAmount
  • 隐式 double--> MoneyAmount
  • 隐式十进制 --> MoneyAmount

您可能想要实现的所有数学运算:

  • MoneyAmount + MoneyAmount
  • MoneyAmount - MoneyAmount
  • MoneyAmount * MoneyAmount
  • MoneyAmount / MoneyAmount

您可能想要实现的所有关系操作:

  • MoneyAmount == MoneyAmount
  • MoneyAmount != MoneyAmount
  • MoneyAmount > MoneyAmount
  • MoneyAmount >= MoneyAmount
  • MoneyAmount
  • MoneyAmount

通过所有这些操作,您已经涵盖了所有基础知识。

【讨论】:

  • 你能扩展一下“所有其他成员”应该是什么吗?
  • OP:请记住,您需要重载所有数学运算符 +-/*&gt;&lt;,并且您可能还希望允许从常见数字类型 double, decimal, int 进行显式转换
  • 好的,谢谢,如果不是要求太多,您能否也举一个“cast”方法的例子?
  • Jamiec 是正确的:所有基本的数学运算和所有转换/转换为其他类型的操作。我会为你添加一些转化。
  • 我在另一个答案@neggenbe 中添加了一些细节
【解决方案2】:

这很快就会变大。正如@MartinMulder 的回答所示,编写结构很容易,但考虑到您需要重载许多运算符组合,以及一些隐式/显式转换。

数学和逻辑运算

考虑您可能想要对MoneyAmount 进行数学运算

  • MoneyAmount + MoneyAmount
  • MoneyAmount + double
  • MoneyAmount + int
  • MoneyAmount + decimal

这是 + 运算符的 4 个重载。冲洗并重复-,/,*(可能还有%)。您还需要重载&lt;&lt;===&gt;&gt;=。这就像 30 个运算符重载。呸!静态方法太多了。

public static MoneyAmount operator +(MoneyAmount d1, double d2) 
{
    return new MoneyAmount((decimal)(d1._value + d2));
}

显式/隐式转换

现在考虑一下,而不是这段代码

MoneyAmount m = new MoneyAmount(1.234);

你想这样做:

MoneyAmount m = 1.234;

这可以通过 隐式 强制转换运算符来实现。

public static implicit operator MoneyAmount(double d)
{
    return new MoneyAmount((decimal)d);
}

(对于要允许隐式转换的每种类型,您都需要一个)

另一个:

int i = 4;
MoneyAmount m = (MoneyAmount)i;

这是通过 显式 强制转换运算符重载来完成的。

public static explicit operator MoneyAmount(double d)
{
    return new MoneyAmount((decimal)d);
}

(同样,每个要允许显式强制转换的类型为 1)

【讨论】:

  • 运算符=不能重载。我认为您的意思是运营商== 不要忘记运营商!=&lt;=&gt;= ;)
  • 每个算子都需要实现一次。如果您有从该类型到 MoneyAmount 的隐式运算符,则不需要为每种类型使用 + 运算符。 C# 将在调用操作符方法之前将其他类型转换为 MoneyAmount。
  • 你看我上一条评论了吗? “这就像 30 个运算符重载”是基于不正确的假设。
  • @MartinMulder 我一直在考虑它... :) int-&gt;double 没有 implicit 演员表。因此,如果您有int n=1 并且您想添加到MoneyAmount 但只有double 的重载,您需要执行MoneyAmount m = 1.23; m+= (double)n;,我觉得这很奇怪。因此double int 的过载 - 我错过了什么吗?
  • 我编辑了我的答案以反映您的问题。存在从 intdouble 的隐式转换。代码'double d = 2;'将编译。
猜你喜欢
  • 2020-01-19
  • 1970-01-01
  • 2011-10-15
  • 1970-01-01
  • 2014-03-04
  • 1970-01-01
  • 2011-06-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多