当我将一个数字相乘,四舍五入然后将其拉回时,如何确保不发生舍入错误?

时间:2018-07-10 19:53:30

标签: rounding rounding-error

我们有需要向其付款的API。在我们这边,我们有一笔初期付款,并加了3%的手续费才能得到总额。 我们将其传递给API,然后将其拆开。它了解3%的费用,并通过取总金额并将其乘以97.09%来将其细分为费用和付款。 我们遇到的问题是,该API仅能保留两位小数,但我们需要花费很多钱。 例如:

payment: $100.01
makes fee of $3.0103
total = payment + fee = 103.0103 (rounded to 103.01)
reverseEngineered = total * percent = 103.01 * .9709
leaves us with 100.012409 (100.01) 

这是正确的,但在此示例中

payment = $333.33
makes fee of 9.9999
total = payment + fee = 343.3299 (rounded to 343.33)
reverseEngineered = total * percent = 343.33 * .9709
leaves us with 333.339097 (333.34) 
333.33 != 333.34 so there is a problem when rounding.

我不控制API,否则我将使百分比更准确(97.08737864%)。

关于如何完成此操作的任何想法,或者没有办法确保它返回无舍入的舍入错误。

做完一些数学运算后,我发现使用%2.99721907作为费用百分比可以计算出更多的数字

{
    Though process x*y = z therefor z / y = x;
    x / .9709 = z therefor z *  .9709 = x
    1 / .9709 = 1.299721907
}

示例

payment = $333.33
makes fee of 9.9906303260
total = payment + fee = 343.3206 (rounded to 343.32)
reverseEngineered = total * percent = 343.32 * .9709
leaves us with 333.329388 (333.33) 

但是我不确定情况是否总是如此。有人知道我可以确定的方法吗?还是这不适用于每个数字?

编辑:

我将更加清楚API的地位。我们没有编写/没有对API的控制。我们正在合作的公司。我们也许可以提出更改建议,但仅此而已。

当我们在其他公司通过API发送付款时,它们将付款和费用分开,并将资金发送到两个单独的帐户。这就是为什么费用需要反向工程的原因

4 个答案:

答案 0 :(得分:0)

由于97.08737864不等于97.09,因此您很困惑。为什么要使用API​​进行如此简单的计算?

答案 1 :(得分:0)

好吧,正如我想您现在所知道的那样,它将永远无法工作-您需要更改api。您无法重新定义数学。

答案 2 :(得分:0)

既然您已经付款并知道手续费,为什么还要重新处理他们的电话号码?

您自己更精确的计算并将其返回给客户吗? (如果是这样的话),并为处理器(或您)的利益承担任何舍入错误的成本。

最好在您的ToS中添加一个免责声明,由于您使用的处理器,四舍五入只能精确到小数点后两位,即yadda yadda。

答案 3 :(得分:0)

我正在php / Laravel中对此进行测试。将起始付款和最终结果以及原始付款和最终结果付款之间的总体差异转储到日志文件中。这是我用于测试的代码。

OAuth2Authentication

将.03用作applyFee函数时,结果像这样

<?php

namespace App\Http\Controllers;

use App\Project;
class ProjectsController extends Controller
{

    public function run(){
        set_time_limit(300000);
        for ($payment = 330; $payment < 340; $payment += .01) {
            //Fix the weird increment problem with floats
            $payment = round($payment,2);
            $fee = $this->applyFee($payment);
            $total = $fee + $payment;
            $returned = $this->passToAPI($total,true,false);
            // \Log::alert($payment . ": " . ($returned["Payment"] - $payment));
            \Log::alert($payment .": " . round($returned["Payment"],2));
            \Log::alert($payment - $returned["Payment"]);

            if($payment != round($returned["Payment"],2)){
                \Log::alert($payment . " is unequal");
            }
        }
    }

    public function passToAPI($value, $round = true, $roundResult = true){
        $rndTotal = $round ? round($value,2) : $value;
        $Payment = $this->reverseEngineerPayment($rndTotal); 
        $Fee = $rndTotal - $Payment;
        $Payment = $roundResult ? round($Payment,2) : $Payment;
        $Fee = $roundResult ? round($Fee,2) : $Fee;
        return  array("Payment" => $Payment, "Fee" => $Fee);
    }

    public function reverseEngineerPayment($total){
        return $total * .9709;
    }
    public function applyFee($payment){
        return $payment * .0299721907;
    }

    public function reverseEngineerFee($total){
        return $total * .0291;
    }
}

由于计算舍入误差,这是一种期望。

但是当我使用
.0299721907 for applyFee函数的结果是这样的

[2018-07-12 13:47:10] local.ALERT: 330: 330.01  
[2018-07-12 13:47:10] local.ALERT: -0.0089099999999576  
[2018-07-12 13:47:10] local.ALERT: 330 is unequal  
[2018-07-12 13:47:10] local.ALERT: 330.01: 330.02  
[2018-07-12 13:47:10] local.ALERT: -0.0086190000000101  
[2018-07-12 13:47:10] local.ALERT: 330.01 is unequal
[2018-07-12 13:47:10] local.ALERT: 330.02: 330.03  
[2018-07-12 13:47:10] local.ALERT: -0.0083280000000059  
[2018-07-12 13:47:10] local.ALERT: 330.02 is unequal  
[2018-07-12 13:47:10] local.ALERT: 330.03: 330.04  
[2018-07-12 13:47:10] local.ALERT: -0.0080370000000016  
[2018-07-12 13:47:10] local.ALERT: 330.03 is unequal  
[2018-07-12 13:47:10] local.ALERT: 330.04: 330.05  
[2018-07-12 13:47:10] local.ALERT: -0.0077459999999974  
[2018-07-12 13:47:10] local.ALERT: 330.04 is unequal
...

它们总是将我传递的结果返还给便士。似乎舍入错误仅会在便士达到后将小数点后的小数点增加,因此结果始终是所需的结果。 注意*我也测试了这个脚本,范围是1到50,000。并且找不到结果不同的任何实例。

我意识到这很笨拙,但据我所知,它仍然有效。如果您发现无法解决此问题的原因,那么我很乐意不使用它,但据我所知它是可靠的。