BCMATH,PHP中的固定点数学,精确损失案例
PHP 和 MySQL 固定点数值运算的挑战与技巧
处理固定点数值时,需要格外小心,尤其是在使用 PHP 和 MySQL 进行开发时。本文将探讨使用 PHP BCMath 扩展、MySQL 固定点表达式处理以及将固定点数据从 PHP 持久化到 MySQL 时遇到的障碍和细节问题。尽管存在一些挑战,我们将尝试找出如何处理固定点数值并避免精度损失的方法。
要点总结
- PHP 中的 BCMath 扩展支持任意精度数学运算,但如果向其函数传递数值型变量,则可能导致精度损失。应改用表示数字的字符串值以避免此问题。
- MySQL 支持固定点数字表达式,但是,如果任何操作数采用指数格式或字符串格式,则会被视为浮点数。PHP PDO 扩展没有用于绑定的 Decimal 参数类型,这可能导致结果不精确。
- 为了在 PHP MySQL 应用程序中执行精确的数学运算,所有运算都可以在 PHP 中处理,并且只有使用 INSERT 或 UPDATE 语句才能将数据持久化到 MySQL。或者,可以手动构建 SQL 查询,确保所有 SQL 数学表达式都以十进制数表示。
BCMath 的问题
BCMath 文档指出:
为了进行任意精度数学运算,PHP 提供了二进制计算器,它支持任何大小和精度的数字,这些数字以字符串表示。
因此,BCMath 函数参数应以字符串表示。将数值型变量传递给 bcmath 函数可能导致错误的结果,与将双精度值视为字符串时出现的精度损失相同。
案例 1
echo bcmul(776.210000, '100', 10) . PHP_EOL; echo bcmul(776.211000, '100', 10) . PHP_EOL; echo bcmul(776.210100, '100', 10) . PHP_EOL; echo bcmul(50018850776.210000, '100', 10) . PHP_EOL; echo bcmul(50018850776.211000, '100', 10) . PHP_EOL; echo bcmul(50018850776.210100, '100', 10) . PHP_EOL;
结果是:
<code>77621.00 77621.100 77621.0100 5001885077621.00 5001885077621.100 5001885077621.00 //此处可见精度损失</code>
切勿将数值型变量传递给 BCMath 函数,只能传递表示数字的字符串值。即使不处理浮点数,BCMath 也会输出奇怪的结果:
案例 2
echo bcmul('10', 0.0001, 10) . PHP_EOL; echo bcmul('10', 0.00001, 10) . PHP_EOL; echo 10*0.00001 . PHP_EOL;
结果是:
<code>0.0010 0 // 这真的很奇怪!!! 0.0001</code>
其原因是 BCMath 将其参数转换为字符串,并且在某些情况下,数字的字符串表示形式具有指数表示法。
案例 3
echo bcmul('10', '1e-4', 10) . PHP_EOL; // 也输出 0
PHP 是一种弱类型语言,在某些情况下,无法严格控制输入——希望处理尽可能多的请求。
例如,我们可以通过应用 sprintf 转换来“修复”案例 2 和 案例 3:
$val = sprintf("%.10f", '1e-5'); echo bcmul('10', $val, 10) . PHP_EOL; // 给我们 0.0001000000
但是,应用相同的转换会破坏案例 1 的“正确”行为:
$val = sprintf("%.10f", '50018850776.2100000000'); echo bcmul('10', $val, 10) . PHP_EOL; echo bcmul('10', 50018850776.2100000000, 10) . PHP_EOL; 500188507762.0999908450 // 错误 500188507762.10 // 正确
因此,sprintf 解决方案不适用于 BCmath。假设所有用户输入都是字符串,我们可以实现一个简单的验证器,捕获所有指数表示法的数字并正确转换它们。此技术在 php-bignumbers 中已实现,因此我们可以安全地传入类似 1e-20 和 50018850776.2101 的参数,而不会丢失精度。
BCMath 最终指导原则
切勿在 BCMath PHP 扩展函数中将浮点数用作固定点运算参数。仅使用字符串。
使用 BCMath 扩展运算时,请注意指数表示法的参数。BCMath 函数无法正确处理指数参数(例如“1e-8”),因此应手动转换它们。注意,不要使用 sprintf 或类似的转换技术,因为这会导致精度损失。
可以使用 php-bignumbers 库,它可以处理指数形式的输入参数,并为用户提供固定点数学运算函数。但是,其性能不如 BCMath 扩展,因此它是在健壮的包和性能之间的一种折衷方案。
MySQL 和固定点数字
在 MySQL 中,固定点数字使用 DECIMAL 列类型处理。您可以阅读 MySQL 官方文档了解数据类型和精确数学运算。
最有趣的部分是 MySQL 如何处理表达式:
数值表达式的处理取决于表达式包含的值的类型:
如果存在任何近似值,则表达式为近似值,并使用浮点运算进行计算。
如果不存在近似值,则表达式仅包含精确值。如果任何精确值包含小数部分(小数点后的值),则使用 DECIMAL 精确算术计算表达式,精度为 65 位数字。“精确”一词受限于可以在二进制中表示的内容。例如,1.0/3.0 可以用十进制表示法近似为 .333…,但不能写成精确数字,因此 (1.0/3.0)*3.0 不精确地计算为 1.0。
否则,表达式仅包含整数值。表达式是精确的,并使用整数算术进行计算,精度与 BIGINT 相同(64 位)。
如果数值表达式包含任何字符串,则将其转换为双精度浮点值,表达式为近似值。
这是一个简短的示例,演示了小数部分的情况:
echo bcmul(776.210000, '100', 10) . PHP_EOL; echo bcmul(776.211000, '100', 10) . PHP_EOL; echo bcmul(776.210100, '100', 10) . PHP_EOL; echo bcmul(50018850776.210000, '100', 10) . PHP_EOL; echo bcmul(50018850776.211000, '100', 10) . PHP_EOL; echo bcmul(50018850776.210100, '100', 10) . PHP_EOL;
这看起来很简单,但让我们看看如何在 PHP 中处理它。
PHP 和 MySQL 中的精确数学运算
因此,现在我们必须将固定点值从 PHP 持久化到 MySQL。正确的方法是在查询中使用预处理语句和占位符。然后我们进行参数绑定,一切都是安全可靠的。
<code>77621.00 77621.100 77621.0100 5001885077621.00 5001885077621.100 5001885077621.00 //此处可见精度损失</code>
当我们将值绑定到语句占位符时,我们可以通过 bindValue 的第三个参数指定其类型。可能的类型由常量 PDO::PARAM_BOOL、PDO::PARAM_NULL、PDO::PARAM_INT、PDO::PARAM_STR、PDO::PARAM_LOB 和 PDO::PARAM_STMT 表示。所以问题是 PHP PDO 扩展没有用于绑定的十进制参数类型。结果,查询中的所有数学表达式都被视为浮点表达式,而不是固定点表达式。
如果我们想利用预处理语句并使用固定点数字,最好的方法是在 PHP 中执行所有数学运算并将结果保存到 MySQL。
echo bcmul('10', 0.0001, 10) . PHP_EOL; echo bcmul('10', 0.00001, 10) . PHP_EOL; echo 10*0.00001 . PHP_EOL;
结论
我们得出以下结论:
- 切勿在 BCMath PHP 扩展函数中将浮点数用作固定点运算参数。仅使用字符串。
- BCMath 扩展不适用于指数表示法的字符串数字。
- MySQL 支持固定点数字表达式,但所有操作数都必须采用十进制格式。如果至少有一个参数采用指数格式或字符串格式,则将其视为浮点数,表达式将作为浮点数进行计算。
- PHP PDO 扩展没有 Decimal 参数类型,因此,如果您使用预处理语句并在包含固定点操作数的 SQL 表达式中绑定参数,则不会获得精确的结果。
- 为了在 PHP MySQL 应用程序中执行精确的数学运算,您可以选择两种方法。第一种方法是在 PHP 中处理所有运算,并且只有使用 INSERT 或 UPDATE 语句才能将数据持久化到 MySQL。在这种情况下,您可以使用预处理语句和参数绑定。第二种方法是手动构建 SQL 查询(您仍然可以使用预处理语句,但必须自行转义参数),以便所有 SQL 数学表达式都以十进制数表示。
我个人最喜欢的方法是第一种:在 PHP 中进行所有数学运算。我同意 PHP 和 MySQL 可能不是需要精确数学运算的应用程序的最佳选择,但是,如果您选择了这种技术堆栈,那么了解如何正确处理它是很好的。
(由于篇幅限制,FAQs 部分被省略。如果需要,可以单独生成FAQs部分。)
以上是BCMATH,PHP中的固定点数学,精确损失案例的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

JWT是一种基于JSON的开放标准,用于在各方之间安全地传输信息,主要用于身份验证和信息交换。1.JWT由Header、Payload和Signature三部分组成。2.JWT的工作原理包括生成JWT、验证JWT和解析Payload三个步骤。3.在PHP中使用JWT进行身份验证时,可以生成和验证JWT,并在高级用法中包含用户角色和权限信息。4.常见错误包括签名验证失败、令牌过期和Payload过大,调试技巧包括使用调试工具和日志记录。5.性能优化和最佳实践包括使用合适的签名算法、合理设置有效期、

会话劫持可以通过以下步骤实现:1.获取会话ID,2.使用会话ID,3.保持会话活跃。在PHP中防范会话劫持的方法包括:1.使用session_regenerate_id()函数重新生成会话ID,2.通过数据库存储会话数据,3.确保所有会话数据通过HTTPS传输。

PHP8.1中的枚举功能通过定义命名常量增强了代码的清晰度和类型安全性。1)枚举可以是整数、字符串或对象,提高了代码可读性和类型安全性。2)枚举基于类,支持面向对象特性,如遍历和反射。3)枚举可用于比较和赋值,确保类型安全。4)枚举支持添加方法,实现复杂逻辑。5)严格类型检查和错误处理可避免常见错误。6)枚举减少魔法值,提升可维护性,但需注意性能优化。

SOLID原则在PHP开发中的应用包括:1.单一职责原则(SRP):每个类只负责一个功能。2.开闭原则(OCP):通过扩展而非修改实现变化。3.里氏替换原则(LSP):子类可替换基类而不影响程序正确性。4.接口隔离原则(ISP):使用细粒度接口避免依赖不使用的方法。5.依赖倒置原则(DIP):高低层次模块都依赖于抽象,通过依赖注入实现。

在PHPStorm中如何进行CLI模式的调试?在使用PHPStorm进行开发时,有时我们需要在命令行界面(CLI)模式下调试PHP�...

使用PHP的cURL库发送JSON数据在PHP开发中,经常需要与外部API进行交互,其中一种常见的方式是使用cURL库发送POST�...

静态绑定(static::)在PHP中实现晚期静态绑定(LSB),允许在静态上下文中引用调用类而非定义类。1)解析过程在运行时进行,2)在继承关系中向上查找调用类,3)可能带来性能开销。
