”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > JavaScript 中的财务精确度:处理金钱而不损失一分钱

JavaScript 中的财务精确度:处理金钱而不损失一分钱

发布于2024-08-30
浏览:746

Financial Precision in JavaScript: Handle Money Without Losing a Cent

在快节奏的金融世界中,精确性就是一切。最好避免因舍入/精度误差而造成数百万美元的损失。

说到金钱,请考虑数据类型

本文的出发点是认识到金钱并不是可以用来数篮子里苹果的平均基本数字。当您尝试将 10 欧元乘以 10 美元时,您会得到什么?很难啊……你有没有在旧夹克的口袋里发现过神奇的 1.546 美元?是的,我知道,这也是不可能的。这些愚蠢的例子是为了说明这样一个事实:金钱有其自己的特定规则,不能仅用简单的数字来建模。我向你保证,我不是第一个意识到这一点的人(也许你比我早意识到这一点)。 2002年,程序员Martin Fowler在《企业应用架构模式》中提出了一种用特定属性和操作数规则来表示货币的方法。对他来说,货币数据类型所需的两个最小可行属性是:

amount
currency

这个真正基本的表示将是我们构建一个简单但强大的货币模型的起点。

金额,如何表示

金额绝对是一个特定的数字:它具有固定的精度(同样,你的口袋里不可能有 4.376 美元)。您需要选择一种能够帮助您尊重此约束的表示方式。

一种朴素的方法,原生数字 JavaScript 数据类型

剧透警告,如果您不想看到几美分(如果不是美元)消失在浮点数表示的黑暗世界中,这绝对不是一个好主意。

代价高昂的精度误差

如果您有一些 JavaScript 编码经验,您就会知道,即使是最简单的计算也可能会导致您一开始没有预料到的精度错误。强调这种现象的最明显和最著名的例子是:

0.1   0.2 !== 0.3 // true
0.1   0.2 // 0.30000000000000004

如果这个例子不能完全说服你,我建议你看看这篇文章,它深入探讨了使用 JavaScript 原生数字类型时可能遇到的所有有问题的计算结果......

结果中的这种微小差异可能对您来说似乎无害(幅度约为 10^-16),但是在关键的金融应用程序中,此类错误可能会迅速级联。考虑在数千个账户之间转移资金,其中每笔交易都涉及类似的计算。这些轻微的误差加起来,在你意识到之前,你的财务报表就已经减少了数千美元。老实说,我们都同意,在金钱方面,错误是不允许的:无论是在法律上还是与客户建立信任关系。

为什么会出现这样的错误?

当我在一个项目中遇到这个问题时,我问自己的第一个问题是为什么?我发现问题的根源不是 JavaScript,这些不精确性也会影响其他现代编程语言(Java、C、Python……)。

// In C
#include 

int main() {
    double a = 0.1;
    double b = 0.2;
    double sum = a   b;
    if (sum == 0.3) {
        printf("Equal\n");
    } else {
        printf("Not Equal\n"); // This block is executed
    }
    return 0;
}

// > Not equal
// In Java
public class doublePrecision {
    public static void main(String[] args) {

        double total = 0;
        total  = 5.6;
        total  = 5.8;
        System.out.println(total);
    }
}

// > 11.399999999999

事实上,根本原因在于这些语言用来表示浮点数的标准:IEEE 754 标准指定的双(或单)精度浮点格式。

IEEE 754 标准:位表示的故事

在Javascript中,原生类型数字对应的是双精度浮点数,也就是说一个数字用64位进行编码,分为三部分:

  • 符号 1 位
  • 11 位指数
  • 52 位尾数(或分数),范围从 0 到 1

Financial Precision in JavaScript: Handle Money Without Losing a Cent

Financial Precision in JavaScript: Handle Money Without Losing a Cent

然后,您需要使用以下公式将您的位表示形式转换为十进制值:

Financial Precision in JavaScript: Handle Money Without Losing a Cent

双精度浮点数表示的示例,近似为 1/3:

0 01111111101 0101010101010101010101010101010101010101010101010101

= (-1)^0 x (1   2^-2   2^-4   2^-6   ...   2^-50   2^-52) x 2^(1021-1023)
= 0.333333333333333314829616256247390992939472198486328125
~ 1/3

这种格式允许我们表示广泛的值,但它不能以绝对精度表示每个可能的数字(仅在 0 和 1 之间,您可以找到无穷多个数字......)。许多数字无法用二进制形式精确表示。循环第一个示例,这就是 0.1 和 0.2 的问题。双点浮点表示为我们提供了这些值的近似值,因此当您将这两个不精确的表示相加时,结果也不精确。

可能的解决方案:任意十进制算术

既然您完全相信使用原生 JavaScript 数字类型处理金额是一个坏主意(至少我希望您开始对此产生怀疑),那么 10 亿美元的问题是 您应该如何进行 ?解决方案可能是利用 JavaScript 中提供的一些强大的固定精度算术包。例如 Decimal.js(流行的 ORM Prisma 使用它来表示其 Decimal 数据类型)或 Big.js。

这些包为您提供了特殊的数据类型,允许您执行计算并消除我们上面解释的精度误差。

// Example using Decimal.js
const Decimal = require('decimal.js');
const a = new Decimal('0.1');
const b = new Decimal('0.2');
const result = a.plus(b);

console.log(result.toString()); // Output: '0.3'

这种方法为您提供了另一个优势,它极大地扩展了可以表示的最大值,例如,当您处理加密货币时,这会变得非常方便。
即使它真的很强大,我也不会选择在我的 Web 应用程序中实现它。我发现应用 Stripe 策略仅处理整数值更容易、更清晰。

向大师学习:Stripe,一种非浮点策略

我们,Theodo Fintech,重视实用主义!我们喜欢从业内最成功的公司中汲取灵感。 Stripe 是一家专注于支付服务的著名的价值数十亿美元的公司,它选择使用整数而不是浮点数来处理金额。为此,他们使用最小的货币单位来表示货币金额。

// 10 USD are represented by
{
 "amount": 1000,
 "currency": "USD"
}

货币最小单位……不一致!

我想你们很多人已经知道这一点:所有货币都不具有相同数量级的最小单位。其中大多数是“两位小数”货币(欧元、美元、英镑),这意味着它们的最小单位是货币的 1/100。然而,有些是“小数点后三位”货币(KWD),甚至是“小数点零位”货币(日元)。 (您可以通过遵循ISO4217标准找到更多相关信息)。为了处理这些差异,您应该将乘法因子集成到您的货币数据表示中,以将以最小单位表示的金额转换为相应的货币。

Financial Precision in JavaScript: Handle Money Without Losing a Cent

我选择了一种表示金额的方式……好的,但是现在,我该如何四舍五入?

我想您已经弄清楚了,您可以使用本机数字、第三方任意精度包或整数,计算可以(并且将会)得出浮点结果,您需要将其四舍五入到您的货币有限精度。举个简单的例子,假设您处理整数值并签订了 16k 美元的贷款合同,利率非常精确,为 8.5413%(哎哟……)。然后您需要退还 16k$ 加上额外的金额

1600000 * 0.085413 // amount in cents
//Output in cents: 136660.8

关键是计算后要正确处理金额的四舍五入过程。大多数时候,您最终不得不在三种不同类型的舍入之间进行选择:

  • 经典舍入:舍入到最接近的值,如果介于两个值之间,则向上舍入
  • 银行家四舍五入: 四舍五入到最接近的值,如果在两个值之间,则向下舍入(如果数字为偶数),如果数字为奇数则向上舍入(当您必须执行大量操作时,这可以为您提供数值稳定性四舍五入)
  • 自定义舍入:基于您使用的货币和您正在处理的用例的特定立法

说到四舍五入,实际上并没有什么“神奇酱”:您需要根据自己的情况进行仲裁。我建议您在处理新货币和新的舍入用例(转换、资金分割、积分利率等)时始终检查立法。您最好立即遵守规定,以避免进一步的麻烦。例如,在汇率方面,大多数货币都确定了所需精度和舍入规则的规则(您可以在此处查看欧元汇率规则)。

结论

本文并未详尽介绍在 JavaScript 中处理金额的所有现有可能性,也无意为您提供完整/完美的货币数据模型。我试图为您提供足够的提示和指南来实施一种被证明是一致的、有弹性的、由金融科技行业的大人物选择的表示形式。我希望您能够在未来的项目中进行金额计算,而不会忘记口袋里的任何一分钱(否则不要忘记看看您的旧夹克)!

版本声明 本文转载于:https://dev.to/benjamin_renoux/financial-precision-in-javascript-handle-money-without-losing-a-cent-1chc?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • 如何有效地选择熊猫数据框中的列?
    如何有效地选择熊猫数据框中的列?
    在处理数据操作任务时,在Pandas DataFrames 中选择列时,选择特定列的必要条件是必要的。在Pandas中,选择列的各种选项。选项1:使用列名 如果已知列索引,请使用ILOC函数选择它们。请注意,python索引基于零。 df1 = df.iloc [:,0:2]#使用索引0和1 c...
    编程 发布于2025-05-01
  • Java是否允许多种返回类型:仔细研究通用方法?
    Java是否允许多种返回类型:仔细研究通用方法?
    在Java中的多个返回类型:一种误解类型:在Java编程中揭示,在Java编程中,Peculiar方法签名可能会出现,可能会出现,使开发人员陷入困境,使开发人员陷入困境。 getResult(string s); ,其中foo是自定义类。该方法声明似乎拥有两种返回类型:列表和E。但这确实是如此吗...
    编程 发布于2025-05-01
  • PHP未来:适应与创新
    PHP未来:适应与创新
    PHP的未来将通过适应新技术趋势和引入创新特性来实现:1)适应云计算、容器化和微服务架构,支持Docker和Kubernetes;2)引入JIT编译器和枚举类型,提升性能和数据处理效率;3)持续优化性能和推广最佳实践。 引言在编程世界中,PHP一直是网页开发的中流砥柱。作为一个从1994年就开始发展...
    编程 发布于2025-05-01
  • input: Why Does "Warning: mysqli_query() expects parameter 1 to be mysqli, resource given" Error Occur and How to Fix It?

output: 解决“Warning: mysqli_query() 参数应为 mysqli 而非 resource”错误的解析与修复方法
    input: Why Does "Warning: mysqli_query() expects parameter 1 to be mysqli, resource given" Error Occur and How to Fix It? output: 解决“Warning: mysqli_query() 参数应为 mysqli 而非 resource”错误的解析与修复方法
    mysqli_query()期望参数1是mysqli,resource给定的,尝试使用mysql Query进行执行MySQLI_QUERY_QUERY formation,be be yessqli:sqli:sqli:sqli:sqli:sqli:sqli: mysqli,给定的资源“可能发...
    编程 发布于2025-05-01
  • 大批
    大批
    [2 数组是对象,因此它们在JS中也具有方法。 切片(开始):在新数组中提取部分数组,而无需突变原始数组。 令ARR = ['a','b','c','d','e']; // USECASE:提取直到索引作...
    编程 发布于2025-05-01
  • `console.log`显示修改后对象值异常的原因
    `console.log`显示修改后对象值异常的原因
    foo = [{id:1},{id:2},{id:3},{id:4},{id:id:5},],]; console.log('foo1',foo,foo.length); foo.splice(2,1); console.log('foo2', foo, foo....
    编程 发布于2025-05-01
  • 如何在php中使用卷发发送原始帖子请求?
    如何在php中使用卷发发送原始帖子请求?
    如何使用php 创建请求来发送原始帖子请求,开始使用curl_init()开始初始化curl session。然后,配置以下选项: curlopt_url:请求 [要发送的原始数据指定内容类型,为原始的帖子请求指定身体的内容类型很重要。在这种情况下,它是文本/平原。要执行此操作,请使用包含以下标头...
    编程 发布于2025-05-01
  • Android如何向PHP服务器发送POST数据?
    Android如何向PHP服务器发送POST数据?
    在android apache httpclient(已弃用) httpclient httpclient = new defaulthttpclient(); httppost httppost = new httppost(“ http://www.yoursite.com/script.p...
    编程 发布于2025-05-01
  • 为什么在我的Linux服务器上安装Archive_Zip后,我找不到“ class \” class \'ziparchive \'错误?
    为什么在我的Linux服务器上安装Archive_Zip后,我找不到“ class \” class \'ziparchive \'错误?
    class'ziparchive'在Linux Server上安装Archive_zip时找不到错误 commant in lin ins in cland ins in lin.11 on a lin.1 in a lin.11错误:致命错误:在... cass中找不到类z...
    编程 发布于2025-05-01
  • 同实例无需转储复制MySQL数据库方法
    同实例无需转储复制MySQL数据库方法
    在同一实例上复制一个MySQL数据库而无需转储在同一mySQL实例上复制数据库,而无需创建InterMediate sqql script。以下方法为传统的转储和IMPORT过程提供了更简单的替代方法。 直接管道数据 MySQL手动概述了一种允许将mysqldump直接输出到MySQL clie...
    编程 发布于2025-05-01
  • 如何使用Regex在PHP中有效地提取括号内的文本
    如何使用Regex在PHP中有效地提取括号内的文本
    php:在括号内提取文本在处理括号内的文本时,找到最有效的解决方案是必不可少的。一种方法是利用PHP的字符串操作函数,如下所示: 作为替代 $ text ='忽略除此之外的一切(text)'; preg_match('#((。 &&& [Regex使用模式来搜索特...
    编程 发布于2025-05-01
  • 如何在GO编译器中自定义编译优化?
    如何在GO编译器中自定义编译优化?
    在GO编译器中自定义编译优化 GO中的默认编译过程遵循特定的优化策略。 However, users may need to adjust these optimizations for specific requirements.Optimization Control in Go Compi...
    编程 发布于2025-05-01
  • Go web应用何时关闭数据库连接?
    Go web应用何时关闭数据库连接?
    在GO Web Applications中管理数据库连接很少,考虑以下简化的web应用程序代码:出现的问题:何时应在DB连接上调用Close()方法?,该特定方案将自动关闭程序时,该程序将在EXITS EXITS EXITS出现时自动关闭。但是,其他考虑因素可能保证手动处理。选项1:隐式关闭终止数...
    编程 发布于2025-05-01
  • CSS强类型语言解析
    CSS强类型语言解析
    您可以通过其强度或弱输入的方式对编程语言进行分类的方式之一。在这里,“键入”意味着是否在编译时已知变量。一个例子是一个场景,将整数(1)添加到包含整数(“ 1”)的字符串: result = 1 "1";包含整数的字符串可能是由带有许多运动部件的复杂逻辑套件无意间生成的。它也可以是故意从单个真理...
    编程 发布于2025-05-01
  • 如何从Google API中检索最新的jQuery库?
    如何从Google API中检索最新的jQuery库?
    从Google APIS 问题中提供的jQuery URL是版本1.2.6。对于检索最新版本,以前有一种使用特定版本编号的替代方法,它是使用以下语法:获取最新版本:未压缩)While these legacy URLs still remain in use, it is recommended ...
    编程 发布于2025-05-01

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3