`
ajoo
  • 浏览: 452718 次
社区版块
存档分类
最新评论

什么时候使用assumption?

阅读更多
问题背景,定义三个概念先:
1。percent。就是百分比。
2。weight。如果percent是10,weight就是0.1,weight=percent/100.
3。amount。如果percent是10,总数是1000,那么amount就是100. amount=total*weight.

在程序中,我们很多时候需要在amount, weight, percent之间来回转换。根据不同模块的需要,把同一个数据点转换成percent或者weight或者amount。

目标是设计一个类Portion,它的接口如下:
public class Portion {
  public double getPercent();
  public double getWeight();
  public double getAmount();
  public double getTotal();
  public static Portion fromPercent(double total, double percent);
  public static Portion fromWeight(double total, double weight);
  public static Portion fromAmount(double total, double amount);
}

另外,程序中还使用这样一个习惯:如果total为0,那么从amount计算percent和weight的时候也是0。(以避免divide by 0)


我的pair想这样实现:

public class Portion {
  private final double total;
  private final double amount;
  Portion(double total, double amount) {
    this.total = total;
    this.amount = amount;
  }
  public double getPercent() {
    return getWeight()*100;
  }
  public double getWeight() {
    return total==0?0:amount/total;
  }
  public double getAmount() {
    return amount;
  }
  public double getTotal() {
    return total;
  }
  public static Portion fromPercent(double total, double percent) {
    return new Portion(total, total*percent/100);
  }
  public static Portion fromWeight(double total, double weight) {
    return new Portion(total, total*weight);
  }
  public static Portion fromAmount(double total, double amount) {
    return new Portion(total, amount);
  }
}


我对这个设计是不满意的。我提出了几点问题:
1。假设在某一点total不可知,那么怎样用Portion来封装唯一可知的percent/weight?Portion.fromWeight(0, 0.1).getPercent()在这个设计中不会返回我期待的10,而是0。
2。double是有误差的。如果客户写一个
assertEquals(weight, Portion.fromWeight(777, weight).getWeight(), 0)

居然也要失败,不免尴尬。

所以我提议这样做:
public class Portion {
  private final double total;
  private final double amount;
  private final double percent;
  private final double weight;
  Portion(double total, double amount, double percent, double weight) {
    this.total = total;
    this.amount = amount;
    this.percent = percent;
    this.weight = weight;
  }
  public double getPercent() {
    return percent;
  }
  public double getWeight() {
    return weight;
  }
  public double getAmount() {
    return amount;
  }
  public double getTotal() {
    return total;
  }
  public static Portion fromPercent(double total, double percent) {
    return new Portion(total, total*percent/100, percent, percent/100);
  }
  public static Portion fromWeight(double total, double weight) {
    return new Portion(total, total*weight, weight*100, weight);
  }
  public static Portion fromAmount(double total, double amount) {
    double weight = total==0?0:amount/total;
    return new Portion(total, amount, weight*100, weight);
  }
}

这样,虽然double计算有误差,我至少可以保证Portion.fromWeight(total, weight).getWeight()永远等于weight。

但是,pair对这种设计感觉不舒服,他不喜欢把逻辑放在静态工厂方法中。
而且,pair对我提出的几个他的方案的缺陷如此回应:
1。total为0的情况本身就特殊。根据整个系统目前的情况,getWeight()和getPercent()都返回0可以接受。
2。我不care是否有误差,反正我们现在比较double的时候都是用一个tolerance的。


我的pair对整个系统了解的比我多,我相信他说的话。只不过,要我做一个单独的小模块,却总要依赖于外部系统的“对特殊情况A,我们可以容忍;对情况B,我们不在乎”这些assumption,让我深藏于心底的洁癖非常不舒服。

我并不是绝对排斥使用适当的assumption来简化实现复杂度。但是当两个方案的复杂度相近时,我很反感把外界的一些assumption引进来。

那么,你怎么想?
分享到:
评论
21 楼 ajoo 2007-08-23  
其实我倒是比较不喜欢在构造函数里面放逻辑。在我看来,构造函数的唯一作用就是:
this.field = value;

当然,什么事情也不能走极端,总是有反例存在的。


pair还真不是那么简单的事情,怎么解决冲突是个大学问啊。当然,我的pair是个特例,不具有代表性。幸好我不用再和他捣浆糊了。
20 楼 hax 2007-08-22  
静态工厂方法为什么不能放逻辑?而且如此简单的逻辑,从哪方面看,都不会造成问题。

我赞同ajoo的想法。实际上我第一眼看到的时候,想到的居然是写三个子类来处理,惭愧。

BTW,总觉的你的pair好奇怪呀。。。俺申请来做你的pair,呵呵。
19 楼 fixopen 2007-08-15  
引用
但是,pair对这种设计感觉不舒服,他不喜欢把逻辑放在静态工厂方法中。


他的理由是什么,应该很有道理啊
18 楼 ajoo 2007-02-19  
complystill 写道
觉得奇怪是正常的啦, 但是为了浮点规范本身的限制去增加自己程序复杂性就不值得了. 如果不是那么直白, 而是间接隐含的碰到 fromAmount(total, amount).getAmount() != amount 的情况和 碰到 1.0d/777*777 != 1.0d 的惊讶程度差不了多少的.

呵呵。我是觉得惊讶程度还是很有差别的。规范本身所带来的惊讶我管不了。但是我不希望我的代码放大这种惊讶。
同时觉得实现复杂性没什么区别。:)
17 楼 歆渊 2007-02-19  
觉得奇怪是正常的啦, 但是为了浮点规范本身的限制去增加自己程序复杂性就不值得了. 如果不是那么直白, 而是间接隐含的碰到 fromAmount(total, amount).getAmount() != amount 的情况和 碰到 1.0d/777*777 != 1.0d 的惊讶程度差不了多少的.

其实二进制浮点数还有很多别的陷阱呢, 搜索过程中发现这篇文章: http://www.concentric.net/~Ttwang/tech/javafloat.htm
16 楼 ajoo 2007-02-19  
complystill 写道
这样似乎是说不能接受在 fromAmount(total, val) 内部对 val 进行运算了哦.

new Double(val) != value 这个基于Java的话想不接受也不行啊, 这是用了autoboxing以后的写法, 按以前的写法是: new Double(val) != new Double(val)
虽然和 new Double(val).doubleValue() 还是有差别, 但都是经过了 对象类型 对 基本类型 的封装这个本质是一样的吧.


btw. 如果 val 的取值是 Double.NaN, 那么 new Double(val).doubleValue() 就是 != val 的:
        double val = Double.NaN;
        System.out.println("NaN == NaN ? "
                + (new Double(val).doubleValue() == val));

结果会是 false

不是。我不是说boxing。是说new Double(val).doubleValue()==val。
也不是说fromAmount(total, amount)内部就不能有计算。内部有什么,我作为用户不那么关心。但是从接口上fromAmount(total, amount).getAmount() != amount就感觉有点怪。

15 楼 歆渊 2007-02-19  
这样似乎是说不能接受在 fromAmount(total, val) 内部对 val 进行运算了哦.

new Double(val) != new Double(val)  这个基于Java的话想不接受也不行啊. 虽然和 new Double(val).doubleValue() 还是有差别, 但都是经过了 对象类型 对 基本类型 的封装这个本质是一样的吧.

另外, 也考虑特殊情况, 如果 val 的取值是 Double.NaN, 那么 new Double(val).doubleValue() 就是 != val 的:
        double val = Double.NaN;
        System.out.println("NaN == NaN ? "
                + (new Double(val).doubleValue() == val));

结果会是 false
14 楼 ajoo 2007-02-19  
complystill 写道
写了段Java程序试试:
    public static void main(String[] args)
    {
        System.out.println("new Double(0.0d).equals(-0.0d) ? "
                + new Double(0.0d).equals(-0.0d));
    }

结果呢? false

用double的时候这种都容忍了, 感觉有点像Object的 == 和 equals(Object o) 的差别了.
0.0d == -0.0d 是 true, 但是 Double.equals 里面是按位比较, 最后就不相等了.
要是再考虑上 NaN 和 正/负 INFINITY 潜在问题就更多了.

另外实现难度可能写代码功夫不用多花多少, 但是测试和将来维护改bug都增加了一些工作量. 我是感觉不太值得, 至少没看过这段程序的人在不知情的情况下第一次见了会先愣一下, 奇怪怎么还用得着这么搞..

我感觉还是是计算机处理整数相对比较完美, 写多了系统程序和一般应用程序, 就会觉得浮点数也应该差不多, 头一次发现居然还有不能相等的情况有点难以接受, 大概做多了就好了吧.

这个不一样吧?0d!=-0d,和val!=val还是两个性质吧?毕竟前者还是通过了运算的,对么?

你觉得new Double(val).doubleValue()!=val可以接受么?
13 楼 歆渊 2007-02-19  
写了段Java程序试试:
    public static void main(String[] args)
    {
        System.out.println("new Double(0.0d).equals(-0.0d) ? "
                + new Double(0.0d).equals(-0.0d));
    }

结果呢? false

用double的时候这种都容忍了, 感觉有点像Object的 == 和 equals(Object o) 的差别了.
0.0d == -0.0d 是 true, 但是 Double.equals 里面是按位比较, 最后就不相等了.
要是再考虑上 NaN 和 正/负 INFINITY 潜在问题就更多了.

另外实现难度可能写代码功夫不用多花多少, 但是测试和将来维护改bug都增加了一些工作量. 我是感觉不太值得, 至少没看过这段程序的人在不知情的情况下第一次见了会先愣一下, 奇怪怎么还用得着这么搞..

我感觉还是是计算机处理整数相对比较完美, 写多了系统程序和一般应用程序, 就会觉得浮点数也应该差不多, 头一次发现居然还有不能相等的情况有点难以接受, 大概做多了就好了吧.
12 楼 ajoo 2007-02-19  
complystill 写道


在这一点上刻意减少误差, 是没法防止系统里其它地方因double精度导致的误差累积的, 到一定程度该出错的时候还是会出错, 倒是这里对精度的特殊处理和其它地方不一致, 反而可能让将来的问题追踪定位更加困惑呢.

嗯。我还是向标准的Double看齐。new Double(val).doubleValue()==val,也没有造成什么更困惑的问题。val==val也没有造成问题。虽然val!=val*777/777。反倒是如果允许val!=val才会造成严重的问题。

我觉得这两者间还是有本质的不同的。经过计算而导致误差客户可以接受,但是没有计算而直接因为内部实现上的一些细节而导致了误差觉得就非常不好。客户可能不关心,但是也可能关心啊。有必要在这里使用一个assumption么?

要说这个不是需求,但是必须有误差也不是需求吧?无论如何,没误差不是总比有误差好?val==val毕竟也满足abs(val-val)<0.1,对么?也就是说,没有误差并不影响你按照有误差的方法来使用它,而相反则不是。

我觉得在两个实现难度上相似的情况下,当然优先选择assumption少的,适应能力高的。
11 楼 歆渊 2007-02-19  
ajoo 写道
complystill 写道
如果需要精确的计算一致性本来就不能用 double, 既然用了double就不应该期望任意运算结果都落在一定的误差范围内.

我觉得:

如果需要很高精度, 就必须把double换成BigDecimal.

如果需要绝对精度, 就要用像IBM的无误差运算库之类的专门解决方案.

double精度造成的suprise不是应用程序的问题, 而是 IEEE FP 规范的问题.

如果说项目需求要求在应用程序里实现基于double的精度保障, 那楼主的方案很好, 但是如果没有这方面的明确需求, 就没有必要了.

double是系统范围内采用的。但是我并不认为采用了double,就可以容忍val != val,或者new Double(val).doubleValue()!=val的情况出现。


ieee标准并没有造成new Double(val).doubleValue()!=val。同样,我也希望fromAmount(total, val).getAmount==val。觉得这个要求不过分。


这个要求感觉不是客户提出来的吧, 用了double的话, 只要稍微有点计算, 就不能期望恒等了呀, val == val/777*777 这个就不能成立了. fromAmount(total, val) 也是逻辑封装, 从外部来看如果 getAmount() 的结果要和它恒等联系起来, 也可以算个高耦合了吧. 至少 getAmount() 的定义, 应该不至于要规范到和构造方法的联系上去吧. 作为构造参数的val值与作为getAmount()返回的值是两个不同概念, 逻辑上是有联系, 但这个联系应该是自然松耦合的联系, 没必要是作为特例而保障出来的联系吧.

有限精度的浮点比较总是需要加上一个容忍范围的, 在机器级别, 甚至 0 == -0 都不成立. 如果其它地方用了 double 的 == 比较, 那或者是非严重错误, 或者是有该上下文的assumption而有意去写成的. 而只有刻意去 fromAmount(total, val).getAmount() 以后马上跟传的 val 进行恒等比较才会觉得蹊跷, 实际程序里真的有这样的逻辑吗?

在这一点上刻意减少误差, 是没法防止系统里其它地方因double精度导致的误差累积的, 到一定程度该出错的时候还是会出错, 倒是这里对精度的特殊处理和其它地方不一致, 反而可能给将来的问题追踪定位增加困惑呢. 虽然如果真的发生了, 这种困惑本来就会很大, 增加一点无足轻重, 但投入的成本也没得到什么好处啊.
10 楼 ajoo 2007-02-18  
pojo 写道
我的fromAmount(double total, double amount)和你的一样是有判断的:
    public static Portion fromAmount(double total, double amount)
    {
        if (total== 0)
            return new Portion(0, 0);
        return new Portion(total, weight/total);
    }
所以fromAmount(777, 1).getAmount() == 1

至于fromAmount(0, 1),我认为最好的办法是抛出异常,因为这是一个不合理的输入,如果让它进入系统,会引起麻烦的。

fromAmount(777, 1).getAmount()在你的方案里很可能返回0.9999999。我期望它返回1。

至于说total为0会造成麻烦。也许吧。但是这个遗留系统里面到处都是对total==0的判断,即使会引起麻烦,也已经是麻烦了。把这个改成抛出异常,虽然可能干净,但是对系统行为的改变是巨大的。
9 楼 ajoo 2007-02-18  
complystill 写道
如果需要精确的计算一致性本来就不能用 double, 既然用了double就不应该期望任意运算结果都落在一定的误差范围内.

我觉得:

如果需要很高精度, 就必须把double换成BigDecimal.

如果需要绝对精度, 就要用像IBM的无误差运算库之类的专门解决方案.

double精度造成的suprise不是应用程序的问题, 而是 IEEE FP 规范的问题.

如果说项目需求要求在应用程序里实现基于double的精度保障, 那楼主的方案很好, 但是如果没有这方面的明确需求, 就没有必要了.

double是系统范围内采用的。但是我并不认为采用了double,就可以容忍val != val,或者new Double(val).doubleValue()!=val的情况出现。


ieee标准并没有造成new Double(val).doubleValue()!=val。同样,我也希望fromAmount(total, val).getAmount==val。觉得这个要求不过分。
8 楼 歆渊 2007-02-18  
如果需要精确的计算一致性本来就不能用 double, 既然用了double就不应该期望任意运算结果都落在一定的误差范围内.

我觉得:

如果需要很高精度, 就必须把double换成BigDecimal.

如果需要绝对精度, 就要用像IBM的无误差运算库之类的专门解决方案.

double精度造成的suprise不是应用程序的问题, 而是 IEEE FP 规范的问题.

如果说项目需求要求在应用程序里实现基于double的精度保障, 那楼主的方案很好, 但是如果没有这方面的明确需求, 就没有必要了.
7 楼 pojo 2007-02-18  
我的fromAmount(double total, double amount)和你的一样是有判断的:
    public static Portion fromAmount(double total, double amount)
    {
        if (total== 0)
            return new Portion(0, 0);
        return new Portion(total, weight/total);
    }
所以fromAmount(777, 1).getAmount() == 1

至于fromAmount(0, 1),我认为最好的办法是抛出异常,因为这是一个不合理的输入,如果让它进入系统,会引起麻烦的。
6 楼 ajoo 2007-02-18  
pojo 写道
fromAmount的签名是
Portion fromAmount(double total, double amount)
fromAmount(1, 0).getAmount()的返回值是0,这应该是对的啊,你的输入是0,输出当然也是0。

在fromAmount(1, 777).getAmount()的例子,输入的值就是不合理的,不管在哪一种实现中都会出错的,这是具体实现的问题。

运算精度的问题,不是强迫客户代码使用精度,而是说对于金额而言,精度超过3位(或4位)是没有意义的,它们在实用上是相等的。运算的最后结果最终要进位到小数点后2位。

1. fromAmount(0, 1).getAmount() != 1
2. fromAmount(777, 1).getAmount() != 1

想想Double,不管double运算是否有误差,
double val = 3.14
new Double(val)==val绝对是保证的。

我希望Portion达到同样的效果。
5 楼 dengyin2000 2007-02-17  
dengyin2000 写道
为Portion定义三个够找函数,是否更好点。 在够找函数里面去计算total;amount;percent;weight;的值


打自己的嘴巴。签名都是一样的
4 楼 pojo 2007-02-17  
fromAmount的签名是
Portion fromAmount(double total, double amount)
fromAmount(1, 0).getAmount()的返回值是0,这应该是对的啊,你的输入是0,输出当然也是0。

在fromAmount(1, 777).getAmount()的例子,输入的值就是不合理的,不管在哪一种实现中都会出错的,这是具体实现的问题。

运算精度的问题,不是强迫客户代码使用精度,而是说对于金额而言,精度超过3位(或4位)是没有意义的,它们在实用上是相等的。运算的最后结果最终要进位到小数点后2位。
3 楼 dengyin2000 2007-02-17  
为Portion定义三个够找函数,是否更好点。 在够找函数里面去计算total;amount;percent;weight;的值
2 楼 ajoo 2007-02-17  
pojo 写道
大家都忙着过年,我来给搂主唱个和。

四个量中,只有两个是自变量,其余两个是导出量。凭这一点,我否定楼主的的做法,因为太多的冗余。剩下的问题是选那两个作基本变量。直觉让我选Total和Percent。这个直觉有没有道理呢?

粗看之下,四个量是相关的,任选其中两个的结果应该是等价的。但细细考究的话还是有一些差别的。实数域对于四则运算是封闭的,只有一点例外,那就是0。0是除法(函数)的奇点。所以我们要尽量避免除法而用乘法。结论就是应该选用Total和Weidht作基本量。这样可以保证
assertEquals(weight, Portion.fromWeight(0, weight).getWeight())
assertEquals(weight, Portion.fromWeight(777, weight).getWeight())
assertEquals(percent, Portion.fromPercent(0, percent).getPercent())
assertEquals(percent, Portion.fromPercent(777, percent).getPercent())
唯一需要做假定的是fromAmount(double total, double amount),如果这个假定还是不能接受,可以让它抛出异常。

至于运算精度,那是另一回事了。既然说的是钱,你要么用assertEquals(A, x, 0.0001)来判是否相等,要么用long或者BigDecimal来实现Portion。

不管冗余与否。那都是内部的实现细节。

问题是你这个方法有两个问题:
fromAmount(1, 0).getAmount() != 1
fromAmount(1, 777).getAmount() != 1

强迫客户代码使用精度?觉得这个api比较不自然。fromAmount(1, x).getAmount()不能原样保持原来的amount?

我认为对客户保持一个最友好的接口,least surprise,比内部是否冗余重要的多。毕竟内部的冗余也都是封装好了的。你就当它是四个计算结果的数好了。
而且,用四个数据点还有容易测试的好处。test case还可以这么写:
assertEquals(new Portion(0, 1, 0, 0), Portion.fromAmount(0, 1));

相关推荐

    Zero Assumption Digital Image Recovery 1.2汉化版 免注册版

    然而,《Zero Assumption Digital Image Recovery 1.2》提供的是免注册版本,意味着用户可以直接使用全部功能,无需支付任何费用。这降低了用户尝试和使用该软件的门槛,也是其受到广大用户欢迎的原因之一。 在使用...

    Gray-World Assumption自动白平衡

    在图像处理领域,"Gray-World Assumption"(灰度世界假设)是一种常见的色彩校正技术,用于自动白平衡。这种假设认为无论拍摄环境如何,一个图像中的所有颜色经过平均后应该趋向于一种中性灰色。自动白平衡的目的是...

    Short and Stateless Signatures from RSA Assumption

    CRYPTO 2009的一篇文章,提出了一个标准模型下基于RSA的短签名方案

    waterloo stat330 note

    The follow examples will illustrate the ideas and ...this be a reasonable assumption? (PROBABILITY MODELS) If we let the random variable X = number of fumbles in a game and assume that the Poisson mod

    Zero Assumption Digital Image Recovery

    在这种情况下,"Zero Assumption Digital Image Recovery"(零假设数字图像恢复)绿色版便成为了一个强大的工具,帮助用户找回那些看似失去的宝贵瞬间。 “Zero Assumption Digital Image Recovery”是一款专为恢复...

    An experimental evaluation of the assumption of independence in multiversion programming

    Leveson在《An Experimental Evaluation of the Assumption of Independence in Multi-Version Programming》中提出的观点。文章的核心是评估多版本编程(N-version programming)中的独立性假设的有效性,并通过...

    Gray World Assumption

    白平衡之Gray World Assumption matlab源码

    照片恢复好用Zero Assumption Digital Image Recovery

    在使用ZADIR时,首先需要连接丢失照片的存储设备,然后运行软件进行扫描。ZADIR会快速识别出可恢复的图像文件,并以预览的形式显示出来。用户可以选择查看这些文件,确认它们是否可成功恢复。此外,ZADIR还提供了...

    low_assumption.rar_CMOS circuit design_low power cmos_power cmos

    4. **低功耗VHDL设计**:在硬件描述语言如VHDL中,可以使用特定的属性和语句来实现低功耗设计,例如,使用“if...else”语句来控制信号的开关,或者使用“wait”语句来暂停进程直到有事件发生。 5. **电源管理**:...

    ID-Based Strong Designated Verifier Signature over -SIS Assumption

    ID-Based Strong Designated Verifier Signature over R-SIS Assumption ID-Based Strong Designated Verifier Signature(基于身份的强指定验证者签名)是一种特殊类型的数字签名方案,旨在确保签名的安全性和签名...

    「安全资讯」Revisiting_the_Secret_Hiding_Assumption_Used_in_Verifia

    「安全资讯」Revisiting_the_Secret_Hiding_Assumption_Used_in_Verifiable_(Outsourced)_Computation - 等级保护 勒索软件 风险评估 安全集成 漏洞挖掘 金融安全

    Short and Stateless Signatures from the RSA Assumption

    此外,作者还使用这一技术对Waters签名进行了全新的安全性分析。Waters签名是目前唯一已知的基于计算Diffie-Hellman假设,在标准模型下既短又无状态的签名方案。 #### 四、方案细节 1. **方案概述**:该签名方案的...

    11 Toolchange according to tool assumption.spp

    零件加工通过刀具假设模拟换刀,并统计换刀时间

    assumption

    Laravel试图通过减轻大多数Web项目中使用的常见任务来减轻开发工作的痛苦,例如: 。 。 用于和存储的多个后端。 富有表现力,直观的 。 数据库不可知。 。 。 Laravel易于访问,但功能强大,可提供大型,强大的...

    basic_assumption:由decent_exposure引入的惯用法的实现,用于在Rails控制器和视图中简明地声明资源

    [sudo] gem install basic_assumption 在Rails应用程序中使用它 对于Rails 2,在environment.rb中: gem . config 'basic_assumption' 对于Rails 3,在您的Gemfile中: gem 'basic_assumption' 要在另一个上下文...

    A_Non_Interactive_Shuffle_Argument_With_Low_Trust_Assumption

    非交互式洗牌论证在低信任假设下的应用 在AI安全和大数据安全领域,确保人才安全体系的构建至关重要。其中一个核心挑战是如何实现自动化渗透测试、安全开发和应对安全威胁。在这一背景下,"A Non-Interactive ...

    An Efficient Strong Designated Verifier Signature Based on R-SIS Assumption

    R-SIS假设是一种基于格理论的 hardness assumption。它假设在给定的格中,找到一个短向量是困难的。R-SIS假设广泛应用于密码学领域,例如数字签名、加密算法和身份验证等。 基于R-SIS假设的SDVS 在本文中,我们...

    Efficient leakage-resilient public key encryption from DDH assumption

    In this paper, we present a new leakage-resilient public key encryption scheme whose security is based on the classical DDH (decisional Diffie-Hellman) assumption. In the computational cost, our ...

    nature deduction

    这类判断的证据是一个允许自由使用假设J1的推导过程,以证明J2。值得注意的是,在推导过程中,并不一定需要使用假设J1。 - **参数判断**(Parametric Judgment)的形式为“对于任何参数a,判断J成立”。这种类型的...

Global site tag (gtag.js) - Google Analytics