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中文网其他相关文章!

PHP仍然流行的原因是其易用性、灵活性和强大的生态系统。1)易用性和简单语法使其成为初学者的首选。2)与web开发紧密结合,处理HTTP请求和数据库交互出色。3)庞大的生态系统提供了丰富的工具和库。4)活跃的社区和开源性质使其适应新需求和技术趋势。

PHP和Python都是高层次的编程语言,广泛应用于Web开发、数据处理和自动化任务。1.PHP常用于构建动态网站和内容管理系统,而Python常用于构建Web框架和数据科学。2.PHP使用echo输出内容,Python使用print。3.两者都支持面向对象编程,但语法和关键字不同。4.PHP支持弱类型转换,Python则更严格。5.PHP性能优化包括使用OPcache和异步编程,Python则使用cProfile和异步编程。

PHP主要是过程式编程,但也支持面向对象编程(OOP);Python支持多种范式,包括OOP、函数式和过程式编程。PHP适合web开发,Python适用于多种应用,如数据分析和机器学习。

PHP起源于1994年,由RasmusLerdorf开发,最初用于跟踪网站访问者,逐渐演变为服务器端脚本语言,广泛应用于网页开发。Python由GuidovanRossum于1980年代末开发,1991年首次发布,强调代码可读性和简洁性,适用于科学计算、数据分析等领域。

PHP适合网页开发和快速原型开发,Python适用于数据科学和机器学习。1.PHP用于动态网页开发,语法简单,适合快速开发。2.Python语法简洁,适用于多领域,库生态系统强大。

PHP在现代化进程中仍然重要,因为它支持大量网站和应用,并通过框架适应开发需求。1.PHP7提升了性能并引入了新功能。2.现代框架如Laravel、Symfony和CodeIgniter简化开发,提高代码质量。3.性能优化和最佳实践进一步提升应用效率。

PHPhassignificantlyimpactedwebdevelopmentandextendsbeyondit.1)ItpowersmajorplatformslikeWordPressandexcelsindatabaseinteractions.2)PHP'sadaptabilityallowsittoscaleforlargeapplicationsusingframeworkslikeLaravel.3)Beyondweb,PHPisusedincommand-linescrip

PHP类型提示提升代码质量和可读性。1)标量类型提示:自PHP7.0起,允许在函数参数中指定基本数据类型,如int、float等。2)返回类型提示:确保函数返回值类型的一致性。3)联合类型提示:自PHP8.0起,允许在函数参数或返回值中指定多个类型。4)可空类型提示:允许包含null值,处理可能返回空值的函数。


热AI工具

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

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

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

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

热门文章

热工具

Atom编辑器mac版下载
最流行的的开源编辑器

SublimeText3 Linux新版
SublimeText3 Linux最新版

mPDF
mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

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

SecLists
SecLists是最终安全测试人员的伙伴。它是一个包含各种类型列表的集合,这些列表在安全评估过程中经常使用,都在一个地方。SecLists通过方便地提供安全测试人员可能需要的所有列表,帮助提高安全测试的效率和生产力。列表类型包括用户名、密码、URL、模糊测试有效载荷、敏感数据模式、Web shell等等。测试人员只需将此存储库拉到新的测试机上,他就可以访问到所需的每种类型的列表。