PHP float 精度

实例 1 $a = 1.1; var_dump(gettype($a)); // string(6) "double" var_dump($a); // float(1.1) 实例 2 $a = "123456789.1100110011"; $a = (float) $a; var_dump($a); // float(123456789.11001) var_dump(sprintf('%.11f', $a)); // string(21) "123456789.11001099646" $b = 123456789.11001; var_dump($b); // float(123456789.11001) var_dump(sprintf('%.11f', $b)); // string(21) "123456789.11000999808" $c = '123456789.1100110011'; $c = (float) $c; var_dump($c); // float(123456789.11001) $c = (string) $c; var_dump($c); // string(15) "123456789.11001" $c = (float) $c; var_dump($c); // float(123456789.11001) var_dump(sprintf('%.11f', $c)); // string(21) "123456789.11000999808" var_dump($a === $b); // bool(false) - 说明 $a 还是携带着 float 的精度 var_dump($b === $c); // bool(true) 实例 3 // # 1 var_dump(120085 === 1200.85 * 100); // bool(false) // # 2 var_dump(120085 == 1200.85 * 100); // bool(false) // # 3 var_dump(120081 == 1200.81 * 100); // bool(true) // # 4 var_dump(120085 - 1200.85 * 100); // float(1.4551915228367E-11) 实例 4 $a = 0.1; $b = 0.9; $c = 1; var_dump(($a + $b) == $c); // bool(true) var_dump(($c - $b) == $a); // bool(false) var_dump(sprintf('%.20f', $a + $b)); // string(22) "1.00000000000000000000" var_dump(sprintf('%.20f', $c - $b)); // string(22) "0.09999999999999997780" var_dump((0.5 - 0.25) === 0.25); // bool(true) 0.5 二进制 0.1,0.25 二进制 0.01 var_dump((0.25 + 0.25) === 0.5); // bool(true) 实例 5 $n = 19.99; var_dump($n * 100); // float(1999) var_dump((int) ($n * 100)); // int(1998) !!! var_dump((string) ($n * 100)); // string(4) "1999" var_dump((int) (string) ($n * 100)); // int(1999) var_dump(round($n * 100)); // float(1999) var_dump((int) round($n * 100)); // int(1999) 19.99 * 100 实际结果是 1998.9999999999998…,(int) 走的是向零截断而非四舍五入,所以掉到 1998。打印时 PHP 默认精度只显示到 1999,肉眼上看不出来这个陷阱。 ...

May 9, 2020 · 3 min · 449 words · Me

Lonicera Framework

项目代码:imzyf/lonicera | GitHub Lonicera Framework - Every French soldier carries a marshal’s baton in his knapsack. MVC MVC 模式的目的是实现一种动态的程序设计,使后续对程序的修改和扩展简化,并且使程序某一部分的重复利用成为可能。 Lonicera 0.1 bootstrap index.php 单一入口模式。 启动 PHP 内置 Web 服务器: php -S localhost:7070 路由器层 更偏向于使用 PATH_INFO 方式来访问。 从传统 URL 参数模式的访问地址进行解析,提取里面的 group、controller、action、param 4 个参数,随后交给 bootstrap 进行 dispatch 处理。 数据模型 用 PDO 来实现连接数据库。 ORM Object Relational Mapping 对象与数据库的映射叫作对象关系映射 PO Persistent Object 把一个数据库中的表的一行记录对应的对象称为持久对象 BO Business Object 业务对象 把业务逻辑封装为一个对象 VO Value Object 值对象 界面显示的数据对象 DTO Data Transfer Object 用在热呵呵需要数据传输的地方 DAO Data Access Object 指代 Active Record 模式中的数据对象 传统的 ORM 模式提倡数据对象和负责持久化的代码的分开,但是这并没有坚持数据操作的工作量。还有一种 ORM 模式叫作 Active Record。在 Active Record 中,模型层集成了 ORM 的功能,他们及代表实体,包含因为业务逻辑,又是数据对象,并负责把自己存储到数据库中。 ...

December 19, 2019 · 2 min · 276 words · Me

最左前缀原理与相关优化

MySQL 中的索引可以按一定顺序引用多个列,这种索引叫做联合索引。形式上,一个联合索引是一个有序元组 <a1, a2, …, an>,其中每个元素是表中的一列;单列索引可视为元素数为 1 的特例。 下文以 MySQL 8.0 + Employees Sample Database 为实验环境。MySQL 5.7 的历史情况会在相关章节穿插说明 —— 5.7 已结束官方支持,但很多旧项目仍在用,行为差异值得标出来。 以 employees.titles 为例,查看其索引。注意 8.0 比 5.7 多了 Visible 与 Expression 两列: SHOW INDEX FROM employees.titles; +--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+ | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression | +--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+ | titles | 0 | PRIMARY | 1 | emp_no | A | 301292 | NULL | NULL | | BTREE | | | YES | NULL | | titles | 0 | PRIMARY | 2 | title | A | 442605 | NULL | NULL | | BTREE | | | YES | NULL | | titles | 0 | PRIMARY | 3 | from_date | A | 442605 | NULL | NULL | | BTREE | | | YES | NULL | +--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+ Visible:8.0 引入的 Invisible Index。ALTER TABLE ... ALTER INDEX idx INVISIBLE 让索引对优化器不可见但物理结构仍在,适合「灰度删除索引」的场景 —— 先标为不可见观察一段时间,没出问题再真删。 Expression:8.0.13+ 的 Functional Key Parts(函数索引)会在这一列展示表达式,后文会用到。 全列匹配 EXPLAIN SELECT * FROM employees.titles WHERE emp_no = '10009' AND title = 'Senior Engineer' AND from_date = '1995-02-18'; +----+-------------+--------+------------+-------+---------------+---------+---------+-------------------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+--------+------------+-------+---------------+---------+---------+-------------------+------+----------+-------+ | 1 | SIMPLE | titles | NULL | const | PRIMARY | PRIMARY | 159 | const,const,const | 1 | 100.00 | NULL | +----+-------------+--------+------------+-------+---------------+---------+---------+-------------------+------+----------+-------+ 对索引所有列进行精确匹配(这里精确匹配指 = 或 IN)时,索引被完整用到。 ...

May 25, 2019 · 6 min · 1155 words · Me, LLM

回顾 2018

重新翻阅的自己工作邮件的发件箱,回顾一年工作。新年伊始自己还是在开发 P 项目的 iOS App,开始写 Q&A 功能。一些不算太难的 tableView 布局的需求,对我来说,都是头大的问题。 这段时期招聘时的面试,竟成为我学习 App 开发的一扇小窗。 年前收到了奖金还是挺开心的。leader 新年寄语: 要发声,要当主力 当有好的想法时,要学会说服别人 要有耐心,Yifan 需要时间的沉淀 前两点意思差不多,这段时间思想上困扰我的是:自己对自己的定位是一个初级工程师,认为会的东西、经验不多,见识少,我尊重比我年长的工程师的想法与观点,也相信他们是经过长远思考过的。这个思维设定,我觉得没有什么问题。但是也许有人忽略了 责任,对方案负责,对项目负责。开发方案一再重建性修改,接口结构没有规范。 2017 的总结说胜利属于伏地魔,本想苟着发育,这时发现:苟是苟不住的,这个世界 需要英雄 carry。 后来我感觉应该将公司看做一个舞台,舞台上有灯光、音效就要利用,展示自己、锻炼自己,即使是出糗,那就整理整理再来一次,who care? 成长是最重要的。 Course 模块是前工程师用 Objective-C 写的,离职后一直没有再维护,过年期间自己重构了所有 Objective-C 的代码,项目完全转为了一个纯 Swift 项目。使用 Realm 作为数据本地化方案,选择的原因也很朴素,GitHub 哪个星多我就优先选用什么。 后来参考 Jack Feng - 6ag 的几个 Swift 开源项目,新创建了 P 项目的工具 App,也对主项目结构做了重新的整理,在这里再次特别感谢。 西安运营部的成立,加多了 C 项目的后台需求,难以都顾及项目两头。使用 laravel-admin 搭建新后台,也开始使用 Docker 部署项目,感觉从此离不开 Docker 了,像极了遇到 Git 时的感觉。 C 项目主站的前端是在服务端渲染,非常传统的模式,多个工程师转手也是十分混乱。参考了 白俊遥 工程的博客、laravel 项目,修改了项目结构,添加了 gulp 工具制定了工作流,虽然没有实现前后端的完全分离,但终究是向现代化前端走出了一步。 转眼就到了年中调薪,公司不含糊,薪水涨到了我满意的值。这对我很关键,调整的不单单是我的薪水,也调整了我的心态。因为当时我认为没有强的工程师、甚至归我负责,却拿着比我还高的薪水。现在总算是有了一个平衡。 不断向 C 项目投入更多的资源,项目 指标 的要求越来越多。比如:优化了项目、优化了查询,到底优化了多少,怎么量化?这些在之前一直不被重视,改好了就都算叫优化了。项目中出现的问题错误,都要查找真实具体的原因,而不是说一个可能的什么原因,就当解答的了。我一开始也很难适应这些,但心里是认同的。 ...

December 31, 2018 · 1 min · 134 words · Me

MySQL 日期/时间类型选型与时区处理

适用版本:MySQL 8.0+ / Laravel 10+。MySQL 5.7 已 EOL,文中不再覆盖。 一句话结论 「物理时刻」(事件发生于何时)→ TIMESTAMP,自动跟随会话时区转换 「字面值」(合同上写的、用户输入的日期)→ DATETIME,原样存取不转换 仅日期 → DATE;仅时长 → TIME;JS/跨语言毫秒戳 → BIGINT 永远不要用 VARCHAR 存日期——无强制约束、无法范围查询、排序按字符串 选型对照表 类型 范围 字节 时区行为 小数秒 典型场景 DATETIME 1000-01-01 00:00:00 ~ 9999-12-31 23:59:59 5 + fsp 字符串原样存取,不转换 DATETIME(0~6) 合同生效时点、用户填写的本地时间、历史事件 TIMESTAMP 1970-01-01 00:00:01 UTC ~ 2038-01-19 03:14:07 UTC 4 + fsp 写入:会话时区 → UTC;读取:UTC → 会话时区 TIMESTAMP(0~6) created_at / updated_at、日志、跨时区事件戳 DATE 1000-01-01 ~ 9999-12-31 3 — — 生日、纪念日、报表日期 TIME -838:59:59 ~ 838:59:59 3 + fsp — TIME(0~6) 营业时长、计时器 YEAR 1901 ~ 2155 1 — — 罕用 BIGINT INT64 8 应用层控制 — JS 毫秒戳、Unix 微秒戳、跨语言互操作 小数秒(fsp,fractional seconds precision) 0~6 位,每 2 位多占 1 字节。Laravel 11+ 默认就用 DATETIME(6) / TIMESTAMP(6)。 ...

May 25, 2018 · 3 min · 468 words · Me, LLM

【Modern PHP】笔记

又回到 PHP Web 开发,使用 Laravel 框架,重读《Modern PHP》。 PHP 正在重生。 特性 命名空间 声明命名空间: <?php namespace Oreilly\ModernPHP; 导入和别名: <?php use Symfony\Component\HttpFoundation\Response as Res; $r = new Res('Oops', 400); $r->send(); PHP 5.6 开始可以导入函数和常量: <?php use func Namespace\functionName; use constant Namespace\CONST_NAME; functionName(); echo CONST_NAME; 使用接口 接口是两个 PHP 对象之间的契约,其目的不是让一个对象依赖另一个对象的身份,而是依赖另一个对象的能力。 使用接口编写更加灵活,能委托别人实现细节。 性状 trait 性状是类的部分实现,可以混入一个或者多个现有的 PHP 类中。性状有两个作用:表明类可以做什么(像是接口);提供模块化实现(像是类)。 如果想让两个无关的 PHP 类具有类似的行为,应该怎么呢?性状就是为了解决这种问题而诞生的。性状能把模块化的实现方式注入多个无关的类中。而且性状还能促进代码的重用。 这与创建一个接口,两个无关的类实现这个接口的优势在于:不用写相同的实现代码,符合 DRY 原则。 PHP 解释器在编译时会把性状复制粘贴到类的定义体中,但是不会处理这个操作引入的不兼容问题。如果性状假定类中有特定的属性和方法(在性状中没有定义),要确保相应的类中有对应的属性和方法。 生成器 Generator 是 PHP 5.5.0 引入的功能。生成器是简单的迭代器,仅此而已。 PHP 生成器不要求类实现 Iterator 接口,从而减轻了类的负担。生成器会根据需求计算并产生要迭代的值。这对应该的性能有重大影响。假如标准的 PHP 迭代器经常在内存中执行迭代操作,这要预先计算出数据集,性能低;此时我们可以使用生成器,即时计算并产出后续值,不占用宝贵的内存资源。 PHP 生成器不能满足所有迭代操作的需求,因为如果不查询,生成器永远不知道下一个要迭代的值是什么,在生成器中无法后退和快进。生成器还是一次性,无法多次迭代同一个生成器。不过,如果需要,可以重建或克隆生成器。 PHP 生成器是 PHP 函数,只不过要在函数中一次或者多次使用 yield 关键字。生成器从不返回值,值产出值。 function myGenerator() { yield 'value1'; yield 'value2'; yield 'value3'; } foreach (myGenerator() as $yieldedValue) { echo $yieldedValue, PHP_EOL; } value1 value2 value3 使用生成器处理 CSV: ...

May 8, 2018 · 3 min · 574 words · Me

用 Certbot 自动化 Let's Encrypt TLS 证书

Certbot 和 Let’s Encrypt 的关系 Let’s Encrypt 一个免费、自动化、开放的公共证书颁发机构(CA)。 通过 ACME(Automatic Certificate Management Environment,RFC 8555)协议向域名所有者颁发 DV(Domain Validation)TLS / SSL 证书。 Certbot 由 Electronic Frontier Foundation(EFF)维护的开源 ACME 客户端。 主要目标是简化与 Let’s Encrypt 之间的交互: 自动化域名验证(HTTP-01、DNS-01、TLS-ALPN-01) 安装并续期证书 更新 Web 服务器配置(Apache、Nginx、Lighttpd 等) Certbot 也能与任何兼容 ACME 的 CA 通信,不限于 Let’s Encrypt。 ACME 挑战怎么选 挑战 适用场景 限制 HTTP-01 80 端口可达、单域名 / 多域名(最常用) 不能签 wildcard DNS-01 任意域名 —— wildcard 唯一选项 需要 DNS provider API 或手动加 TXT TLS-ALPN-01 443 可达但 80 被封,常用于反代后端 较少用,需要 ACME-aware 反代支持 典型工作流程 解析参数并检测服务器类型。 选择并执行挑战(HTTP-01 例:在 /.well-known/acme-challenge/ 下写入 token)。 Let’s Encrypt 回访验证域名归属。 验证通过后签发证书;Certbot 下载并安装到本地。 创建 systemd timer(snap 安装会自动配置)周期性 certbot renew 续期。 开始实验 实验环境:Amazon Linux 2023(AL2023)。AL2023 没有官方 certbot 包,社区维护的 snap 仓库是当前最省事的安装路径。 ...

April 26, 2018 · 4 min · 815 words · Me, LLM

【Git 权威指南】读书笔记 - 协同

【Git 权威指南】“和声”+“协同模型"两章的精炼合并版:远程协议、推送与冲突解决、里程碑共享、分支协同、工作流模型、共用代码(submodule / subtree)。 本文沿用书中 master 作为默认分支名;Git 2.28+ 默认改为 main,行为完全一致。 远程协议与版本库 协议选择 协议 用途 现状 SSH 推 + 拉,需公钥认证 协作首选 HTTPS 推 + 拉,凭证由 helper 缓存 网络受限环境首选;个人项目常用 本地 file:// 或裸路径 备份 / 镜像 git:// 只读匿名协议 已弃用,GitHub 2022 年关停 FTP / RSYNC — 已弃用,忘了它们 ssh://[user@]example.com[:port]/path/to/repo.git [user@]example.com:path/to/repo.git # SSH 简写 https://example.com/path/to/repo.git 远程版本库管理 git remote -v # 列出 fetch/push URL git remote add <name> <url> # 注册 git remote set-url <name> <url> # 改 URL git remote set-url --push <name> <url> # 单独改 push URL(读写分离) git remote rename <old> <new> git remote rm <name> git remote update # 拉取所有 remote 的更新 git config remote.<name>.skipDefaultUpdate true # 排除某个 remote fetch / pull 的关系 git pull = git fetch + git merge # 默认 git pull --rebase = git fetch + git rebase 习惯用 rebase 整理历史的人,全局开启: ...

January 17, 2018 · 5 min · 952 words · Me, LLM

回顾 2017

2017 关键词:Birds、离职、狼人杀、白洋淀、iOS、自如。 Birds Birds 是自己编写时间最长的一个 Web 项目。项目接手、改版、重构、迭代、盈利,上半年每天工作都是面对它。 Birds 良好的表现与需求互相推动,这点很重要,假如有需求而项目没有成长,或项目成长没了需求,对工程来说都只能是纸上谈兵、无事可做。项目中的规范的重要性,无论是代码还是数据库,只要项目不是夭折终将显现,对于规范性我绝不会开倒车,做任何妥协,种种经历只说明:这里欠的东西总会还。如何应对需求的千变万化,小结几点:模块化、组件化思想,功能多可配置,功能可拔插,功能方法粒度要小;不要对程序肆意进行打补丁式的修改,减少对流程入侵;当某一处的逻辑增加时要及时重新修改、定义流程;对需求目的的正确理解更是能高效开发。 对前端重新认识的一年。现在的前端和大学时刚刚接触时,已经大不一样了。前端能做的事情越来越多,很多业务逻辑都在前端处理,服务端只需要提供接口,分工合作更容易。浅尝 React Vue Webpack 后,也许前端才是自己的归宿。 离职 离职的不是我。老人走,新人来,新人走。离职一词在我身边跟了一年都没有停下来。同事离职的原因很多,离开北京、公司发展、自我发展… 马云说离职的原因就两个:1. 钱没给够。2. 受了委屈。我起初是认同这一说法的,但是后来觉得“人”或者说“人的思想”或者说“人性”在其中影响作用非常的大。相同的事情不同的角度看,结果相去甚远,如果基本的理念不同,那事情基本是无法调和的。 对做的事情的认同感,对自我的定位与认识,对现实情况的冷静思考,这几点需要反复咀嚼。 分享一篇文章:公司没大牛带,需要离职么?。对于刚入门的我们,这是一个很有代表性的问题。太多时候我们期待别人,忘了自己。公司有大牛,能跟着学是幸运,没有是常态。先接受了这个设定,会更容易找提高自己的方法。有没有大牛并不重要,我们是为了那个更好的自己。 狼人杀 2017 火的游戏很多:狼人杀、守望先锋、王者荣耀、吃鸡。狼人杀这一杀,好像杀到了毕业前和同学舍友在一起臭 high 的日子。可说骚话、互相 diss、互相吹捧,真真假假其乐无穷。最赤鸡的是鱼炸出了个女盆友,各路同学亲上加亲,无法克说。 白洋淀 我想 你说 你不要在孤单 让我做你的伴 白洋淀去过四次,那里的温泉过去两次,年中 Team Building 再喜加一,而这一次让这里有了美丽故事。喷泉、沙发,出现了对的人。 iOS React Native 的一波尝试后,走上了移动开发的道。Swift 让上 iOS 车的门槛降低不少,Swift API 趋向稳定,可以说这是最好的时候。GitHub 应该是再也放不下了,外文的书籍、文档确实靠谱啊,推荐一个教程网站 iOS Tutorials - Ray Wenderlich 真的零基础入门。 我相信:一个人语言的界限,就是他世界的边界。 自如 一场大火让无数人无家可归,年末换房也受牵连。被自如圈粉,其他中介的房子真的太 low 了。自如订房子居然也要抢,一波三折、失而复得。在没抢到房子时,真的想走,想离开这里了,很凄凉。最终是在公司对面住下了,过上了在家吃中饭晚饭的日子。 2018 Happy New Year 2017 自己是幸运的,2018 猥琐发育,不要浪,胜利属于伏地魔。 – EOF –

December 31, 2017 · 1 min · 67 words · Me

【Git 权威指南】读书笔记 - 独奏

【Git 权威指南】“初识 Git” + “独奏"两章的精炼合并本:背景、安装、初始化、暂存区、对象模型、引用游标(reset / checkout / stash)、里程碑、文件操作、历史查看、改变历史、克隆。 本文沿用书中 master 作为默认分支名。Git 2.28+ 默认改为 main,行为完全一致。 前传:diff、patch、分布式 Git 之前,源码协作靠 diff + patch 这套老组合: diff -u hello.old hello.new > hello.patch # -u: unified 格式,带上下文,是补丁的必备参数 patch hello.old < hello.patch # 升级到 new patch -R hello.old < hello.patch # -R: 反向,回到 old patch -p1 < changes.patch # -p1: 跳过路径首层,用于跨目录补丁 补丁文件长这样: --- hello.old 2026-05-14 10:00:00 +++ hello.new 2026-05-14 10:05:00 @@ -1,3 +1,3 @@ hello -world +git ! Git 的 git diff / git apply / git format-patch / git am 就是这套机制的强化版 —— 加了对象寻址、历史图谱、合并算法。 ...

July 19, 2017 · 9 min · 1742 words · Me, LLM