- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
之前开发的接口需要用到json加签,有一次对接JAVA时,签名怎么都过不了,仔细对比了字符串,发现是PHP进行json_encode时,会将浮点型所有无意义的0给去掉(echo和var_dump也会),而JAVA那边没有。遂在文档中写下: “json中请把无意义的0去掉”。 #doge 。
最近又遇到这个事情,需求直接要求:显示字符型,且精度要保留两位小数,于是不得不开始研究PHP的json中,浮点型的精度该如何保留的问题.
删除无意义0的原理在这里:文内跳转:原理-PHP中浮点型的显示 。
需求的解决方法在这里:文内跳转:字符串处理-正则 。
下面是整体的解决过程和相关原理.
json_encode的函数原型如下: json_encode(mixed $value, int $flags = 0, int $depth = 512): string|false 。
众所周知,json_encode的第一个进阶用法,就是它的第二个参数flags,也就是“可选的json编码方式”,各种奇妙的常量。比如我最长用到的,JSON_UNESCAPED_UNICODE,让json不自动进行unicode转换,直接输出中文。所以第一个想到的,就是查看有没有对应的常量参数.
查看源码,json的常量参数都放在 php-src/ext/json/php_json.h 中,如下:
/* json_encode() options */
#define PHP_JSON_HEX_TAG (1<<0)
#define PHP_JSON_HEX_AMP (1<<1)
#define PHP_JSON_HEX_APOS (1<<2)
#define PHP_JSON_HEX_QUOT (1<<3)
#define PHP_JSON_FORCE_OBJECT (1<<4)
#define PHP_JSON_NUMERIC_CHECK (1<<5)
#define PHP_JSON_UNESCAPED_SLASHES (1<<6)
#define PHP_JSON_PRETTY_PRINT (1<<7)
#define PHP_JSON_UNESCAPED_UNICODE (1<<8)
#define PHP_JSON_PARTIAL_OUTPUT_ON_ERROR (1<<9)
#define PHP_JSON_PRESERVE_ZERO_FRACTION (1<<10)
#define PHP_JSON_UNESCAPED_LINE_TERMINATORS (1<<11)
PHP_JSON_UNESCAPED_UNICODE,恰好对应的就是256,二进制的设计是为了他们可以方便的复合使用。写法也很多变,比如json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),json_encode($data, JSON_UNESCAPED_UNICODE + JSON_UNESCAPED_SLASHES),json_encode($data, 256 + 64)。都是一样的实现.
PHP json_encode中文文档 PHP json_encode常量文档 。
文内跳转:常量版本适用性 。
其中和数字有关的,就是PHP_JSON_NUMERIC_CHECK,以及PHP_JSON_PRESERVE_ZERO_FRACTION.
// 将所有数字字符串编码成数字(numbers)。
// Encodes numeric strings as numbers.
JSON_NUMERIC_CHECK (int)
// 确保 float 值始终编码为为 float 值。
// Ensures that float values are always encoded as a float value.
JSON_PRESERVE_ZERO_FRACTION (int)
$str_arr = [
'str1' => '1',
'str2' => '1.0',
'str3' => '1.00',
'str4' => '1.1',
'str5' => '1.10',
'str6' => '1.110'
];
$s_j1 = json_encode($str_arr, JSON_NUMERIC_CHECK);
$s_j2 = json_encode($str_arr, JSON_PRESERVE_ZERO_FRACTION);
$s_j3 = json_encode($str_arr, JSON_NUMERIC_CHECK | JSON_PRESERVE_ZERO_FRACTION);
echo $s_j1,PHP_EOL;
echo $s_j2,PHP_EOL;
echo $s_j3,PHP_EOL;
echo PHP_EOL;
$float_arr = [
'f1' => 1,
'f2' => 1.0,
'f3' => 1.00,
'f4' => 1.1,
'f5' => 1.10,
'f6' => 1.110
];
$f_j1 = json_encode($float_arr, JSON_NUMERIC_CHECK);
$f_j2 = json_encode($float_arr, JSON_PRESERVE_ZERO_FRACTION);
$f_j3 = json_encode($float_arr, JSON_NUMERIC_CHECK | JSON_PRESERVE_ZERO_FRACTION);
echo $f_j1,PHP_EOL;
echo $f_j2,PHP_EOL;
echo $f_j3,PHP_EOL;
{"str1":1,"str2":1,"str3":1,"str4":1.1,"str5":1.1}
{"str1":"1","str2":"1.0","str3":"1.00","str4":"1.1","str5":"1.10"}
{"str1":1,"str2":1.0,"str3":1.0,"str4":1.1,"str5":1.1}
{"f1":1,"f2":1,"f3":1,"f4":1.1,"f5":1.1}
{"f1":1,"f2":1.0,"f3":1.0,"f4":1.1,"f5":1.1}
{"f1":1,"f2":1.0,"f3":1.0,"f4":1.1,"f5":1.1}
可以看到JSON_NUMERIC_CHECK正如文档描述中的那样,将所有数字字符串都编码成了数字,无意义的0仍旧会被处理掉.
而JSON_PRESERVE_ZERO_FRACTION的表现形式就有些奇怪,只能在有第一位小数且为0时,只保留一位0.
文内跳转:JSON_PRESERVE_ZERO_FRACTION的处理 。
显然,flags是无法满足需求的.
文档中有这么一句话 。
如果参数是 array 或 object,则会递归序列化.
编码受传入的 flags 参数影响,此外浮点值的编码依赖于 serialize_precision.
serialize_precision文档位置 。
serialize_precision int
序列化浮点数时存储的有效数字的位数。-1 表示将使用增强算法来四舍五入此类数字。
PHP中,serialize_precision配置项用于序列化时控制浮点数的精度,而precision用于平常显示时的控制.
我们取一个数字,echo json_encode(17.2);,将serialize_precision,从低到高设置。得到下面的结果:
0 2.0e+1
1 2.0e+1
2 17
3 17.2
4 17.2
可以比较清楚的看出这个配置的效果了,而且显然,无法达成需求.
测试时发现,在PHP7.1以上的版本中,如果将serialize_precision的数值设置为很大,比如5.*版本默认的17,得到的结果是: 17.199999999999999。precision同理,作用于echo,var_dump,print_r等.
所以建议日常使用,设置为默认的-1就好.
如此来看,从编码配置层面似乎无法解决这个需求了,那么就使用最简单直接的办法: 用正则,直接对字符串下手.
foreach ($data as &$item) {
if (is_numeric($item)) {
$item = sprintf("%.2f", $item);
}
}
$json = json_encode($data);
// 浮点型转换为数值型
$pattern = '/"(\d+\.\d+)"/';
$replacement = '$1';
$new_json = preg_replace($pattern, $replacement, $json);
这段函数,是把数值全部先转换为保留2位小数的字符串,进行json_encode后,再把字符串中所有带".",左右是数字的,外层的双引号去掉.
如果你的json更为复杂,需要对正则进行调整.
我们来看这么一段代码,猜测下他的输出结果会是什么:
echo 1.0;
var_dump(1);
var_dump(1.0);
var_dump(1.0 === 1);
var_dump(1.00 === 1.0);
结果
1
int(1)
float(1)
bool(false)
bool(true)
那么,为什么会出现float(1),1.00 === 1.0这样奇怪的输出呢?原因在于PHP内核中变量容器Zval(Zend value)的实现,以及显示处理.
PHP是一个弱类型语言,一个变量,可以是任何类型,这也得益于Zval的实现。Zval,也就是_zval_struct这个结构体,主要记录了三块东西:值,类型,引用计数。并没有“显示精度”这种属性和配置。(引用计数和垃圾回收有关) 。
所以在var_dump时,显示的是变量的类型float,以及和存储的值,最近似的有意义的数值,也就是float(1)。而使用===对比时,存储的值相等,类型也相等,自然就会显示成true.
// Zend\zend_smart_str.c
ZEND_API void ZEND_FASTCALL smart_str_append_double(
smart_str *str, double num, int precision, bool zero_fraction) {
char buf[ZEND_DOUBLE_MAX_LENGTH];
/* Model snprintf precision behavior. */
zend_gcvt(num, precision ? precision : 1, '.', 'E', buf);
smart_str_appends(str, buf);
if (zero_fraction && zend_finite(num) && !strchr(buf, '.')) {
smart_str_appendl(str, ".0", 2);
}
}
JSON_PRESERVE_ZERO_FRACTION 是在这里进行的影响,会在最终判断是否整形,并加".0" 。
// ext\standard\var.c
PHPAPI zend_result php_var_export_ex(zval *struc, int level, smart_str *buf) {
...
case IS_DOUBLE:
smart_str_append_double(
buf, Z_DVAL_P(struc), (int) PG(serialize_precision), /* zero_fraction */ true);
break;
...
}
// Zend\zend_ast.c
static ZEND_COLD void zend_ast_export_zval(smart_str *str, zval *zv, int priority, int indent) {
...
case IS_DOUBLE:
smart_str_append_double(
str, Z_DVAL_P(zv), (int) EG(precision), /* zero_fraction */ false);
break;
...
}
可以很明显的看到,serialize_precision和precision,就是从这里进行的引入.
// Zend\zend_strtod.c
ZEND_API char *zend_gcvt(double value, int ndigit, char dec_point, char exponent, char *buf) {
...
if ((decpt >= 0 && decpt > ndigit) || decpt < -3) { /* use E-style */
/* exponential format (e.g. 1.2345e+13) */
...
} else if (decpt < 0) {
/* standard format 0. */
*dst++ = '0'; /* zero before decimal point */
*dst++ = dec_point;
do {
*dst++ = '0';
} while (++decpt < 0);
src = digits;
while (*src != '\0') {
*dst++ = *src++;
}
*dst = '\0';
} else {
/* standard format */
for (i = 0, src = digits; i < decpt; i++) {
if (*src != '\0') {
*dst++ = *src++;
} else {
*dst++ = '0';
}
}
if (*src != '\0') {
if (src == digits) {
*dst++ = '0'; /* zero before decimal point */
}
*dst++ = dec_point;
for (i = decpt; digits[i] != '\0'; i++) {
*dst++ = digits[i];
}
}
*dst = '\0';
}
zend_freedtoa(digits);
return (buf);
}
e的写法,清除无意义的0,在这里被实现.
如果要显示确切的精度,只能转换为字符串类型,有两种方法:
$number = 1;
echo sprintf("%.2f", $number);
echo number_format($number, 2, '.', '');
两种方法都在PHP4的版本实装,可以放心使用.
需要注意的是,如果本身的位数超过精度,这两种方法都会四舍五入.
另外,number_format的第三个参数为“小数点符号”,第四个参数为“千位分隔符”。默认分别是"."和","。尤其是需要进行数字计算和正常显示时,需要注意“千位分隔符”的设置.
PHP中的浮点型,是使用c中的double型实现的,全部都是遵循 IEEE754 标准,64位的双精度浮点数,不存在单精度.
在PHP中,double和float的命名使用的很混乱。在源码中,多见double,类型判断用的也是IS_DOUBLE。但在7以后,显示定义的类型,必须使用float。比如 function(float $num): float,这似乎是为了与其他语言的命名方式保持一致.
获取类型的相关函数,使用不同版本进行了简单测试,很奇怪,尽量别用8.2:
gettype(1.0); // double
var_dump(1.0); // 8.2版本显示为double,8.3及其他版本都是float,同时8.2版本也多出了文件位置的输出
其他函数
// 都只是别名,功能一致
is_float();
is_double();
floatval();
doubleval();
...
PHP float型文档 。
从float文档中可以看到,由于精度问题,官方是不支持把浮点型进行直接对比和计算的,“永远不要相信浮点数结果精确到了最后一位,也永远不要比较两个浮点数是否相等”。(例如,0.1 + 0.2 在计算机中并不等于 0.3,而是等于 0.30000000000000004) 。
一般正常的四则运算其实影响不大,但如果对精度有很高的要求,推荐使用BC系列函数,或者GMP函数.
对比前,先使用round()函数,将浮点型进行四舍五入处理。(和官方给的处理方式类似,但更好理解) 。
$x = 8 - 6.4; // which is equal to 1.6
$y = 1.6;
var_dump($x == $y); // is not true
PHP thinks that 1.6 (coming from a difference) is not equal to 1.6. To make it work, use round()
var_dump(round($x, 2) == round($y, 2)); // this is true
This happens probably because $x is not really 1.6, but 1.599999.. and var_dump shows it to you as being 1.6.
7.4以后,支持对浮点型添加下划线,只是增加可读性,和千分符类似:
1_000.0 == 1000.0; // true
阅读json_encode文档时,还可以发现, 。
JsonSerializable 文档位置 。
实现 JsonSerializable 的类可以 在 json_encode() 时定制他们的 JSON 表示法(序列化).
go的json序列化比较常见,可以结合理解.
JAVA也有同名JsonSerializable方法,是将类信息也带入json中,可以实现反序列化,不常用.
class IDou implements JsonSerializable
{
public function __construct(protected $name, protected $year)
{}
public function jsonSerialize()
{
return ['name' => $this->name, 'year' => $this->year];
}
}
echo json_encode(new IDou('cxk', 2.5));
结果:
{"name":"cxk","year":2.5}
最后此篇关于PHP的json浮点精度难题的文章就讲到这里了,如果你想了解更多关于PHP的json浮点精度难题的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我想将 JavaScript 函数移动到 来自输入标签的标签,但它不起作用。 这个有效: 这不是: function FieldOnKeyUp() { this.value=this.
我遇到了这个问题:说给定两个权重1和3,您可以权衡1,2(乘以3-1),3,4(乘以3 + 1)(使用平衡的两面)。现在找到最小的砝码数量,以便可以测量1到1000。 答案是1,3,9,27 ...
这是代码 war 的套路,我似乎无法弄明白。我以前从未使用过 JavaScript。 我知道答案可能很简单,但即使经过许多小时的搜索,我似乎也无法弄清楚他们在寻找什么。我知道 greet 函数中的 n
在完成一项学校作业时,我有一个抽象类 Person、一个扩展 Person 的抽象类 Student 和一个扩展学生的普通类 CollegeStudent。 CollegeStudent 从文件中读取
下面的代码让我很头疼 var somearr = [1, 2, 3]; function operations() { for (var i
我在 3 个文件中有以下代码: Defines.h #ifndef Defines_h extern const unsigned int SIZE; #endif Defines.cpp #incl
我的任务是尝试创建一个从文本文档中删除个人信息的自动化系统。 电子邮件、电话号码相对容易删除。名字不是。这个问题很难,因为文档中有需要保留的名称(例如,引用资料、名人、人物等)。需要从内容中删除作者姓
我卡在这里了... #include #define DBG_LVL(lvl, stmt) \ do{ \ if(lvl>1) printf stmt; \ }while(0) #defi
我正在尝试使用动态编程解决类似桥梁和 torch 的问题。有关此问题的更多信息,请参见维基百科 (http://en.wikipedia.org/wiki/Bridge_and_torch_probl
我有数组 A[0...N]的 double和数组 B[0...N]的 int .每B[i]变化在 [0...P] .我只需要计算数组 C[0...P] : C[j] = SUM( A[i] : B[i
我目前在使用 jQuery 中的scrollTop() 函数时遇到困难。目前,平滑滚动功能正在滚动经过预期部分,然后在功能完成运行后弹回该部分。我在本文末尾添加了一个 jsFiddle,但这是我目前的
PHP代码 $t = strtotime( '2012-09-21T03:00:00+00:00 America/Chicago' ); $t2 = date('c',$t); echo $t2;
我知道使用 .运算符将函数链接在一起,如下所示: isLessThanZero x | x a -> a -> a 还可以看到: subtract :: Num a => a -> (a ->
PHP代码 $t = strtotime( '2012-09-21T03:00:00+00:00 America/Chicago' ); $t2 = date('c',$t); echo $t2;
我创建了两个 jar 文件 my.common.jar,其中包含辅助类和方法(主要是静态方法)。我还创建了一个 jar 文件 test.jar,其中包含一个 main 方法,该方法调用 my.comm
已解决:@Desolator 已让我的代码在下面的评论中完全正常工作 好的,所以我创建了 3 个类,它们都相互链接: 启动画面 > 项目分配 > CompareSignature 我想谈论的类是闪屏类
我正在尝试使用 firestore 的 .where() 功能来检测某个字符串是否在数据库的数组中。我曾尝试通过添加方括号和其他东西来表示数组的一部分来操纵函数的第一个参数,但无济于事。 //in t
我有一个 PHP 系统,允许用户以 1 - 5 的范围对照片进行投票,我想要做的是突出显示两个人给彼此相同的投票/分数的地方。我目前无法弄清楚我的 PHP 函数的 SQL。 数据库看起来像这样 id,
我在使用 SQLAlchemy 处理 Unicode 时遇到了一个奇怪的问题。简而言之,当我将 Python unicode 字符串插入 Unicode 列时我的 MySQL 数据库,我可以毫不费力地
我正在尝试使用 Selenium 自动执行 Google 翻译网络界面(但无需了解 Selenium 即可理解此问题,只需要知道它会找到元素并单击它们即可)。我一直在选择要翻译的语言。 我无法打开下拉
我是一名优秀的程序员,十分优秀!