mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-09-25 20:56:42 +08:00
1611 lines
37 KiB
HTML
1611 lines
37 KiB
HTML
<!DOCTYPE html>
|
||
|
||
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
|
||
|
||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||
|
||
<head>
|
||
|
||
<head>
|
||
|
||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
|
||
|
||
<link rel="icon" href="/static/favicon.png">
|
||
|
||
<title>09 数值计算:注意精度、舍入和溢出问题.md.html</title>
|
||
|
||
<!-- Spectre.css framework -->
|
||
|
||
<link rel="stylesheet" href="/static/index.css">
|
||
|
||
<!-- theme css & js -->
|
||
|
||
<meta name="generator" content="Hexo 4.2.0">
|
||
|
||
</head>
|
||
|
||
|
||
|
||
<body>
|
||
|
||
|
||
|
||
<div class="book-container">
|
||
|
||
<div class="book-sidebar">
|
||
|
||
<div class="book-brand">
|
||
|
||
<a href="/">
|
||
|
||
<img src="/static/favicon.png">
|
||
|
||
<span>技术文章摘抄</span>
|
||
|
||
</a>
|
||
|
||
</div>
|
||
|
||
<div class="book-menu uncollapsible">
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="/" class="current-tab">首页</a></li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="../">上一级</a></li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/00 开篇词 业务代码真的会有这么多坑?.md.html">00 开篇词 业务代码真的会有这么多坑?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/01 使用了并发工具类库,线程安全就高枕无忧了吗?.md.html">01 使用了并发工具类库,线程安全就高枕无忧了吗?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/02 代码加锁:不要让“锁”事成为烦心事.md.html">02 代码加锁:不要让“锁”事成为烦心事.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/03 线程池:业务代码最常用也最容易犯错的组件.md.html">03 线程池:业务代码最常用也最容易犯错的组件.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/04 连接池:别让连接池帮了倒忙.md.html">04 连接池:别让连接池帮了倒忙.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/05 HTTP调用:你考虑到超时、重试、并发了吗?.md.html">05 HTTP调用:你考虑到超时、重试、并发了吗?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/06 2成的业务代码的Spring声明式事务,可能都没处理正确.md.html">06 2成的业务代码的Spring声明式事务,可能都没处理正确.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/07 数据库索引:索引并不是万能药.md.html">07 数据库索引:索引并不是万能药.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/08 判等问题:程序里如何确定你就是你?.md.html">08 判等问题:程序里如何确定你就是你?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
<a class="current-tab" href="/专栏/Java 业务开发常见错误 100 例/09 数值计算:注意精度、舍入和溢出问题.md.html">09 数值计算:注意精度、舍入和溢出问题.md.html</a>
|
||
|
||
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/10 集合类:坑满地的List列表操作.md.html">10 集合类:坑满地的List列表操作.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/11 空值处理:分不清楚的null和恼人的空指针.md.html">11 空值处理:分不清楚的null和恼人的空指针.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/12 异常处理:别让自己在出问题的时候变为瞎子.md.html">12 异常处理:别让自己在出问题的时候变为瞎子.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/13 日志:日志记录真没你想象的那么简单.md.html">13 日志:日志记录真没你想象的那么简单.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/14 文件IO:实现高效正确的文件读写并非易事.md.html">14 文件IO:实现高效正确的文件读写并非易事.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/15 序列化:一来一回你还是原来的你吗?.md.html">15 序列化:一来一回你还是原来的你吗?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/16 用好Java 8的日期时间类,少踩一些“老三样”的坑.md.html">16 用好Java 8的日期时间类,少踩一些“老三样”的坑.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/17 别以为“自动挡”就不可能出现OOM.md.html">17 别以为“自动挡”就不可能出现OOM.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/18 当反射、注解和泛型遇到OOP时,会有哪些坑?.md.html">18 当反射、注解和泛型遇到OOP时,会有哪些坑?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/19 Spring框架:IoC和AOP是扩展的核心.md.html">19 Spring框架:IoC和AOP是扩展的核心.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/20 Spring框架:框架帮我们做了很多工作也带来了复杂度.md.html">20 Spring框架:框架帮我们做了很多工作也带来了复杂度.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/21 代码重复:搞定代码重复的三个绝招.md.html">21 代码重复:搞定代码重复的三个绝招.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/22 接口设计:系统间对话的语言,一定要统一.md.html">22 接口设计:系统间对话的语言,一定要统一.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/23 缓存设计:缓存可以锦上添花也可以落井下石.md.html">23 缓存设计:缓存可以锦上添花也可以落井下石.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/24 业务代码写完,就意味着生产就绪了?.md.html">24 业务代码写完,就意味着生产就绪了?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/25 异步处理好用,但非常容易用错.md.html">25 异步处理好用,但非常容易用错.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/26 数据存储:NoSQL与RDBMS如何取长补短、相辅相成?.md.html">26 数据存储:NoSQL与RDBMS如何取长补短、相辅相成?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/27 数据源头:任何客户端的东西都不可信任.md.html">27 数据源头:任何客户端的东西都不可信任.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/28 安全兜底:涉及钱时,必须考虑防刷、限量和防重.md.html">28 安全兜底:涉及钱时,必须考虑防刷、限量和防重.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/29 数据和代码:数据就是数据,代码就是代码.md.html">29 数据和代码:数据就是数据,代码就是代码.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/30 如何正确保存和传输敏感数据?.md.html">30 如何正确保存和传输敏感数据?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/31 加餐1:带你吃透课程中Java 8的那些重要知识点(一).md.html">31 加餐1:带你吃透课程中Java 8的那些重要知识点(一).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/32 加餐2:带你吃透课程中Java 8的那些重要知识点(二).md.html">32 加餐2:带你吃透课程中Java 8的那些重要知识点(二).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/33 加餐3:定位应用问题,排错套路很重要.md.html">33 加餐3:定位应用问题,排错套路很重要.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/34 加餐4:分析定位Java问题,一定要用好这些工具(一).md.html">34 加餐4:分析定位Java问题,一定要用好这些工具(一).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/35 加餐5:分析定位Java问题,一定要用好这些工具(二).md.html">35 加餐5:分析定位Java问题,一定要用好这些工具(二).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/36 加餐6:这15年来,我是如何在工作中学习技术和英语的?.md.html">36 加餐6:这15年来,我是如何在工作中学习技术和英语的?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/37 加餐7:程序员成长28计.md.html">37 加餐7:程序员成长28计.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/38 加餐8:Java程序从虚拟机迁移到Kubernetes的一些坑.md.html">38 加餐8:Java程序从虚拟机迁移到Kubernetes的一些坑.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/答疑篇:代码篇思考题集锦(一).md.html">答疑篇:代码篇思考题集锦(一).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/答疑篇:代码篇思考题集锦(三).md.html">答疑篇:代码篇思考题集锦(三).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/答疑篇:代码篇思考题集锦(二).md.html">答疑篇:代码篇思考题集锦(二).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/答疑篇:加餐篇思考题答案合集.md.html">答疑篇:加餐篇思考题答案合集.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/答疑篇:安全篇思考题答案合集.md.html">答疑篇:安全篇思考题答案合集.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/答疑篇:设计篇思考题答案合集.md.html">答疑篇:设计篇思考题答案合集.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/结束语 写代码时,如何才能尽量避免踩坑?.md.html">结束语 写代码时,如何才能尽量避免踩坑?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
|
||
|
||
<div class="sidebar-toggle-inner"></div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<script>
|
||
|
||
function add_inner() {
|
||
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
|
||
inner.classList.add('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
function remove_inner() {
|
||
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
|
||
inner.classList.remove('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
function sidebar_toggle() {
|
||
|
||
let sidebar_toggle = document.querySelector('.sidebar-toggle')
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let content = document.querySelector('.off-canvas-content')
|
||
|
||
if (sidebar_toggle.classList.contains('extend')) { // show
|
||
|
||
sidebar_toggle.classList.remove('extend')
|
||
|
||
sidebar.classList.remove('hide')
|
||
|
||
content.classList.remove('extend')
|
||
|
||
} else { // hide
|
||
|
||
sidebar_toggle.classList.add('extend')
|
||
|
||
sidebar.classList.add('hide')
|
||
|
||
content.classList.add('extend')
|
||
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
function open_sidebar() {
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
|
||
sidebar.classList.add('show')
|
||
|
||
overlay.classList.add('show')
|
||
|
||
}
|
||
|
||
function hide_canvas() {
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
|
||
sidebar.classList.remove('show')
|
||
|
||
overlay.classList.remove('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
</script>
|
||
|
||
|
||
|
||
<div class="off-canvas-content">
|
||
|
||
<div class="columns">
|
||
|
||
<div class="column col-12 col-lg-12">
|
||
|
||
<div class="book-navbar">
|
||
|
||
<!-- For Responsive Layout -->
|
||
|
||
<header class="navbar">
|
||
|
||
<section class="navbar-section">
|
||
|
||
<a onclick="open_sidebar()">
|
||
|
||
<i class="icon icon-menu"></i>
|
||
|
||
</a>
|
||
|
||
</section>
|
||
|
||
</header>
|
||
|
||
</div>
|
||
|
||
<div class="book-content" style="max-width: 960px; margin: 0 auto;
|
||
|
||
overflow-x: auto;
|
||
|
||
overflow-y: hidden;">
|
||
|
||
<div class="book-post">
|
||
|
||
<p id="tip" align="center"></p>
|
||
|
||
<div><h1>09 数值计算:注意精度、舍入和溢出问题</h1>
|
||
|
||
<p>你好,我是朱晔。今天,我要和你说说数值计算的精度、舍入和溢出问题。</p>
|
||
|
||
<p>之所以要单独分享数值计算,是因为很多时候我们习惯的或者说认为理所当然的计算,在计算器或计算机看来并不是那么回事儿。就比如前段时间爆出的一条新闻,说是手机计算器把 10%+10% 算成了 0.11 而不是 0.2。</p>
|
||
|
||
<p>出现这种问题的原因在于,国外的计算程序使用的是单步计算法。在单步计算法中,a+b% 代表的是 a*(1+b%)。所以,手机计算器计算 10%+10% 时,其实计算的是 10%*(1+10%),所以得到的是 0.11 而不是 0.2。</p>
|
||
|
||
<p>在我看来,计算器或计算机会得到反直觉的计算结果的原因,可以归结为:</p>
|
||
|
||
<p>在人看来,浮点数只是具有小数点的数字,0.1 和 1 都是一样精确的数字。但,计算机其实无法精确保存浮点数,因此浮点数的计算结果也不可能精确。</p>
|
||
|
||
<p>在人看来,一个超大的数字只是位数多一点而已,多写几个 1 并不会让大脑死机。但,计算机是把数值保存在了变量中,不同类型的数值变量能保存的数值范围不同,当数值超过类型能表达的数值上限则会发生溢出问题。</p>
|
||
|
||
<p>接下来,我们就具体看看这些问题吧。</p>
|
||
|
||
<h2>“危险”的 Double</h2>
|
||
|
||
<p>我们先从简单的反直觉的四则运算看起。对几个简单的浮点数进行加减乘除运算:</p>
|
||
|
||
<pre><code>System.out.println(0.1+0.2);
|
||
|
||
|
||
|
||
System.out.println(1.0-0.8);
|
||
|
||
|
||
|
||
System.out.println(4.015*100);
|
||
|
||
|
||
|
||
System.out.println(123.3/100);
|
||
|
||
|
||
|
||
double amount1 = 2.15;
|
||
|
||
|
||
|
||
double amount2 = 1.10;
|
||
|
||
|
||
|
||
if (amount1 - amount2 == 1.05)
|
||
|
||
|
||
|
||
System.out.println("OK");
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>输出结果如下:</p>
|
||
|
||
<pre><code>0.30000000000000004
|
||
|
||
|
||
|
||
0.19999999999999996
|
||
|
||
|
||
|
||
401.49999999999994
|
||
|
||
|
||
|
||
1.2329999999999999
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>可以看到,输出结果和我们预期的很不一样。比如,0.1+0.2 输出的不是 0.3 而是 0.30000000000000004;再比如,对 2.15-1.10 和 1.05 判等,结果判等不成立。</p>
|
||
|
||
<p>出现这种问题的主要原因是,计算机是以二进制存储数值的,浮点数也不例外。Java 采用了IEEE 754 标准实现浮点数的表达和运算,你可以通过这里查看数值转化为二进制的结果。</p>
|
||
|
||
<p>比如,0.1 的二进制表示为 0.0 0011 0011 0011… (0011 无限循环),再转换为十进制就是 0.1000000000000000055511151231257827021181583404541015625。对于计算机而言,0.1 无法精确表达,这是浮点数计算造成精度损失的根源。</p>
|
||
|
||
<p>你可能会说,以 0.1 为例,其十进制和二进制间转换后相差非常小,不会对计算产生什么影响。但,所谓积土成山,如果大量使用 double 来作大量的金钱计算,最终损失的精度就是大量的资金出入。比如,每天有一百万次交易,每次交易都差一分钱,一个月下来就差 30 万。这就不是小事儿了。那,如何解决这个问题呢?</p>
|
||
|
||
<p>我们大都听说过 BigDecimal 类型,浮点数精确表达和运算的场景,一定要使用这个类型。不过,在使用 BigDecimal 时有几个坑需要避开。我们用 BigDecimal 把之前的四则运算改一下:</p>
|
||
|
||
<pre><code>System.out.println(new BigDecimal(0.1).add(new BigDecimal(0.2)));
|
||
|
||
|
||
|
||
System.out.println(new BigDecimal(1.0).subtract(new BigDecimal(0.8)));
|
||
|
||
|
||
|
||
System.out.println(new BigDecimal(4.015).multiply(new BigDecimal(100)));
|
||
|
||
|
||
|
||
System.out.println(new BigDecimal(123.3).divide(new BigDecimal(100)));
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>输出如下:</p>
|
||
|
||
<pre><code>0.3000000000000000166533453693773481063544750213623046875
|
||
|
||
|
||
|
||
0.1999999999999999555910790149937383830547332763671875
|
||
|
||
|
||
|
||
401.49999999999996802557689079549163579940795898437500
|
||
|
||
|
||
|
||
1.232999999999999971578290569595992565155029296875
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>可以看到,运算结果还是不精确,只不过是精度高了而已。这里给出浮点数运算避坑第一原则:使用 BigDecimal 表示和计算浮点数,且务必使用字符串的构造方法来初始化 BigDecimal:</p>
|
||
|
||
<pre><code>System.out.println(new BigDecimal("0.1").add(new BigDecimal("0.2")));
|
||
|
||
|
||
|
||
System.out.println(new BigDecimal("1.0").subtract(new BigDecimal("0.8")));
|
||
|
||
|
||
|
||
System.out.println(new BigDecimal("4.015").multiply(new BigDecimal("100")));
|
||
|
||
|
||
|
||
System.out.println(new BigDecimal("123.3").divide(new BigDecimal("100")));
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>改进后,就能得到我们想要的输出了:</p>
|
||
|
||
<pre><code>0.3
|
||
|
||
|
||
|
||
0.2
|
||
|
||
|
||
|
||
401.500
|
||
|
||
|
||
|
||
1.233
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>到这里,你可能会继续问,不能调用 BigDecimal 传入 Double 的构造方法,但手头只有一个 Double,如何转换为精确表达的 BigDecimal 呢?</p>
|
||
|
||
<p>我们试试用 Double.toString 把 double 转换为字符串,看看行不行?</p>
|
||
|
||
<pre><code>System.out.println(new BigDecimal("4.015").multiply(new BigDecimal(Double.toString(100))));
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>输出为 401.5000。与上面字符串初始化 100 和 4.015 相乘得到的结果 401.500 相比,这里为什么多了 1 个 0 呢?原因就是,BigDecimal 有 scale 和 precision 的概念,scale 表示小数点右边的位数,而 precision 表示精度,也就是有效数字的长度。</p>
|
||
|
||
<p>调试一下可以发现,new BigDecimal(Double.toString(100)) 得到的 BigDecimal 的 scale=1、precision=4;而 new BigDecimal(“100”) 得到的 BigDecimal 的 scale=0、precision=3。对于 BigDecimal 乘法操作,返回值的 scale 是两个数的 scale 相加。所以,初始化 100 的两种不同方式,导致最后结果的 scale 分别是 4 和 3:</p>
|
||
|
||
<pre><code>private static void testScale() {
|
||
|
||
|
||
|
||
BigDecimal bigDecimal1 = new BigDecimal("100");
|
||
|
||
|
||
|
||
BigDecimal bigDecimal2 = new BigDecimal(String.valueOf(100d));
|
||
|
||
|
||
|
||
BigDecimal bigDecimal3 = new BigDecimal(String.valueOf(100));
|
||
|
||
|
||
|
||
BigDecimal bigDecimal4 = BigDecimal.valueOf(100d);
|
||
|
||
|
||
|
||
BigDecimal bigDecimal5 = new BigDecimal(Double.toString(100));
|
||
|
||
|
||
|
||
print(bigDecimal1); //scale 0 precision 3 result 401.500
|
||
|
||
|
||
|
||
print(bigDecimal2); //scale 1 precision 4 result 401.5000
|
||
|
||
|
||
|
||
print(bigDecimal3); //scale 0 precision 3 result 401.500
|
||
|
||
|
||
|
||
print(bigDecimal4); //scale 1 precision 4 result 401.5000
|
||
|
||
|
||
|
||
print(bigDecimal5); //scale 1 precision 4 result 401.5000
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
private static void print(BigDecimal bigDecimal) {
|
||
|
||
|
||
|
||
log.info("scale {} precision {} result {}", bigDecimal.scale(), bigDecimal.precision(), bigDecimal.multiply(new BigDecimal("4.015")));
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>BigDecimal 的 toString 方法得到的字符串和 scale 相关,又会引出了另一个问题:对于浮点数的字符串形式输出和格式化,我们应该考虑显式进行,通过格式化表达式或格式化工具来明确小数位数和舍入方式。接下来,我们就聊聊浮点数舍入和格式化。</p>
|
||
|
||
<h2>考虑浮点数舍入和格式化的方式</h2>
|
||
|
||
<p>除了使用 Double 保存浮点数可能带来精度问题外,更匪夷所思的是这种精度问题,加上 String.format 的格式化舍入方式,可能得到让人摸不着头脑的结果。</p>
|
||
|
||
<p>我们看一个例子吧。首先用 double 和 float 初始化两个 3.35 的浮点数,然后通过 String.format 使用 %.1f 来格式化这 2 个数字:</p>
|
||
|
||
<pre><code>double num1 = 3.35;
|
||
|
||
|
||
|
||
float num2 = 3.35f;
|
||
|
||
|
||
|
||
System.out.println(String.format("%.1f", num1));//四舍五入
|
||
|
||
|
||
|
||
System.out.println(String.format("%.1f", num2));
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>得到的结果居然是 3.4 和 3.3。</p>
|
||
|
||
<p>这就是由精度问题和舍入方式共同导致的,double 和 float 的 3.35 其实相当于 3.350xxx 和 3.349xxx:</p>
|
||
|
||
<pre><code>3.350000000000000088817841970012523233890533447265625
|
||
|
||
|
||
|
||
3.349999904632568359375
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>String.format 采用四舍五入的方式进行舍入,取 1 位小数,double 的 3.350 四舍五入为 3.4,而 float 的 3.349 四舍五入为 3.3。</p>
|
||
|
||
<p>我们看一下 Formatter 类的相关源码,可以发现使用的舍入模式是 HALF_UP(代码第 11 行):</p>
|
||
|
||
<pre><code>else if (c == Conversion.DECIMAL_FLOAT) {
|
||
|
||
|
||
|
||
// Create a new BigDecimal with the desired precision.
|
||
|
||
|
||
|
||
int prec = (precision == -1 ? 6 : precision);
|
||
|
||
|
||
|
||
int scale = value.scale();
|
||
|
||
|
||
|
||
if (scale > prec) {
|
||
|
||
|
||
|
||
// more "scale" digits than the requested "precision"
|
||
|
||
|
||
|
||
int compPrec = value.precision();
|
||
|
||
|
||
|
||
if (compPrec <= scale) {
|
||
|
||
|
||
|
||
// case of 0.xxxxxx
|
||
|
||
|
||
|
||
value = value.setScale(prec, RoundingMode.HALF_UP);
|
||
|
||
|
||
|
||
} else {
|
||
|
||
|
||
|
||
compPrec -= (scale - prec);
|
||
|
||
|
||
|
||
value = new BigDecimal(value.unscaledValue(),
|
||
|
||
|
||
|
||
scale,
|
||
|
||
|
||
|
||
new MathContext(compPrec));
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>如果我们希望使用其他舍入方式来格式化字符串的话,可以设置 DecimalFormat,如下代码所示:</p>
|
||
|
||
<pre><code>double num1 = 3.35;
|
||
|
||
|
||
|
||
float num2 = 3.35f;
|
||
|
||
|
||
|
||
DecimalFormat format = new DecimalFormat("#.##");
|
||
|
||
|
||
|
||
format.setRoundingMode(RoundingMode.DOWN);
|
||
|
||
|
||
|
||
System.out.println(format.format(num1));
|
||
|
||
|
||
|
||
format.setRoundingMode(RoundingMode.DOWN);
|
||
|
||
|
||
|
||
System.out.println(format.format(num2));
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>当我们把这 2 个浮点数向下舍入取 2 位小数时,输出分别是 3.35 和 3.34,还是我们之前说的浮点数无法精确存储的问题。</p>
|
||
|
||
<p>因此,即使通过 DecimalFormat 来精确控制舍入方式,double 和 float 的问题也可能产生意想不到的结果,所以浮点数避坑第二原则:浮点数的字符串格式化也要通过 BigDecimal 进行。</p>
|
||
|
||
<p>比如下面这段代码,使用 BigDecimal 来格式化数字 3.35,分别使用向下舍入和四舍五入方式取 1 位小数进行格式化:</p>
|
||
|
||
<pre><code>BigDecimal num1 = new BigDecimal("3.35");
|
||
|
||
|
||
|
||
BigDecimal num2 = num1.setScale(1, BigDecimal.ROUND_DOWN);
|
||
|
||
|
||
|
||
System.out.println(num2);
|
||
|
||
|
||
|
||
BigDecimal num3 = num1.setScale(1, BigDecimal.ROUND_HALF_UP);
|
||
|
||
|
||
|
||
System.out.println(num3);
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>这次得到的结果是 3.3 和 3.4,符合预期。</p>
|
||
|
||
<h2>用 equals 做判等,就一定是对的吗?</h2>
|
||
|
||
<p>现在我们知道了,应该使用 BigDecimal 来进行浮点数的表示、计算、格式化。在上一讲介绍判等问题时,我提到一个原则:包装类的比较要通过 equals 进行,而不能使用 ==。那么,使用 equals 方法对两个 BigDecimal 判等,一定能得到我们想要的结果吗?</p>
|
||
|
||
<p>我们来看下面的例子。使用 equals 方法比较 1.0 和 1 这两个 BigDecimal:</p>
|
||
|
||
<pre><code>System.out.println(new BigDecimal("1.0").equals(new BigDecimal("1")))
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>你可能已经猜到我要说什么了,结果当然是 false。BigDecimal 的 equals 方法的注释中说明了原因,equals 比较的是 BigDecimal 的 value 和 scale,1.0 的 scale 是 1,1 的 scale 是 0,所以结果一定是 false:</p>
|
||
|
||
<pre><code>/**
|
||
|
||
|
||
|
||
\* Compares this {@code BigDecimal} with the specified
|
||
|
||
|
||
|
||
\* {@code Object} for equality. Unlike {@link
|
||
|
||
|
||
|
||
\* #compareTo(BigDecimal) compareTo}, this method considers two
|
||
|
||
|
||
|
||
\* {@code BigDecimal} objects equal only if they are equal in
|
||
|
||
|
||
|
||
\* value and scale (thus 2.0 is not equal to 2.00 when compared by
|
||
|
||
|
||
|
||
\* this method).
|
||
|
||
|
||
|
||
*
|
||
|
||
|
||
|
||
\* @param x {@code Object} to which this {@code BigDecimal} is
|
||
|
||
|
||
|
||
\* to be compared.
|
||
|
||
|
||
|
||
\* @return {@code true} if and only if the specified {@code Object} is a
|
||
|
||
|
||
|
||
\* {@code BigDecimal} whose value and scale are equal to this
|
||
|
||
|
||
|
||
\* {@code BigDecimal}'s.
|
||
|
||
|
||
|
||
\* @see #compareTo(java.math.BigDecimal)
|
||
|
||
|
||
|
||
\* @see #hashCode
|
||
|
||
|
||
|
||
*/
|
||
|
||
|
||
|
||
@Override
|
||
|
||
|
||
|
||
public boolean equals(Object x)
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>如果我们希望只比较 BigDecimal 的 value,可以使用 compareTo 方法,修改后代码如下:</p>
|
||
|
||
<pre><code>System.out.println(new BigDecimal("1.0").compareTo(new BigDecimal("1"))==0);
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>学过上一讲,你可能会意识到 BigDecimal 的 equals 和 hashCode 方法会同时考虑 value 和 scale,如果结合 HashSet 或 HashMap 使用的话就可能会出现麻烦。比如,我们把值为 1.0 的 BigDecimal 加入 HashSet,然后判断其是否存在值为 1 的 BigDecimal,得到的结果是 false:</p>
|
||
|
||
<pre><code>Set<BigDecimal> hashSet1 = new HashSet<>();
|
||
|
||
|
||
|
||
hashSet1.add(new BigDecimal("1.0"));
|
||
|
||
|
||
|
||
System.out.println(hashSet1.contains(new BigDecimal("1")));//返回false
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>解决这个问题的办法有两个:</p>
|
||
|
||
<p>第一个方法是,使用 TreeSet 替换 HashSet。TreeSet 不使用 hashCode 方法,也不使用 equals 比较元素,而是使用 compareTo 方法,所以不会有问题。</p>
|
||
|
||
<pre><code>Set<BigDecimal> treeSet = new TreeSet<>();
|
||
|
||
|
||
|
||
treeSet.add(new BigDecimal("1.0"));
|
||
|
||
|
||
|
||
System.out.println(treeSet.contains(new BigDecimal("1")));//返回true
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>第二个方法是,把 BigDecimal 存入 HashSet 或 HashMap 前,先使用 stripTrailingZeros 方法去掉尾部的零,比较的时候也去掉尾部的 0,确保 value 相同的 BigDecimal,scale 也是一致的:</p>
|
||
|
||
<pre><code>Set<BigDecimal> hashSet2 = new HashSet<>();
|
||
|
||
|
||
|
||
hashSet2.add(new BigDecimal("1.0").stripTrailingZeros());
|
||
|
||
|
||
|
||
System.out.println(hashSet2.contains(new BigDecimal("1.000").stripTrailingZeros()));//返回true
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<h2>小心数值溢出问题</h2>
|
||
|
||
<p>数值计算还有一个要小心的点是溢出,不管是 int 还是 long,所有的基本数值类型都有超出表达范围的可能性。</p>
|
||
|
||
<p>比如,对 Long 的最大值进行 +1 操作:</p>
|
||
|
||
<pre><code>long l = Long.MAX_VALUE;
|
||
|
||
|
||
|
||
System.out.println(l + 1);
|
||
|
||
|
||
|
||
System.out.println(l + 1 == Long.MIN_VALUE);
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>输出结果是一个负数,因为 Long 的最大值 +1 变为了 Long 的最小值:</p>
|
||
|
||
<pre><code>-9223372036854775808
|
||
|
||
|
||
|
||
true
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>显然这是发生了溢出,而且是默默地溢出,并没有任何异常。这类问题非常容易被忽略,改进方式有下面 2 种。</p>
|
||
|
||
<p>方法一是,考虑使用 Math 类的 addExact、subtractExact 等 xxExact 方法进行数值运算,这些方法可以在数值溢出时主动抛出异常。我们来测试一下,使用 Math.addExact 对 Long 最大值做 +1 操作:</p>
|
||
|
||
<pre><code>try {
|
||
|
||
|
||
|
||
long l = Long.MAX_VALUE;
|
||
|
||
|
||
|
||
System.out.println(Math.addExact(l, 1));
|
||
|
||
|
||
|
||
} catch (Exception ex) {
|
||
|
||
|
||
|
||
ex.printStackTrace();
|
||
|
||
|
||
|
||
}
|
||
|
||
</code></pre>
|
||
|
||
<p>执行后,可以得到 ArithmeticException,这是一个 RuntimeException:</p>
|
||
|
||
<pre><code>java.lang.ArithmeticException: long overflow
|
||
|
||
|
||
|
||
at java.lang.Math.addExact(Math.java:809)
|
||
|
||
|
||
|
||
at org.geekbang.time.commonmistakes.numeralcalculations.demo3.CommonMistakesApplication.right2(CommonMistakesApplication.java:25)
|
||
|
||
|
||
|
||
at org.geekbang.time.commonmistakes.numeralcalculations.demo3.CommonMistakesApplication.main(CommonMistakesApplication.java:13)
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>方法二是,使用大数类 BigInteger。BigDecimal 是处理浮点数的专家,而 BigInteger 则是对大数进行科学计算的专家。</p>
|
||
|
||
<p>如下代码,使用 BigInteger 对 Long 最大值进行 +1 操作;如果希望把计算结果转换一个 Long 变量的话,可以使用 BigInteger 的 longValueExact 方法,在转换出现溢出时,同样会抛出 ArithmeticException:</p>
|
||
|
||
<pre><code>BigInteger i = new BigInteger(String.valueOf(Long.MAX_VALUE));
|
||
|
||
|
||
|
||
System.out.println(i.add(BigInteger.ONE).toString());
|
||
|
||
|
||
|
||
try {
|
||
|
||
|
||
|
||
long l = i.add(BigInteger.ONE).longValueExact();
|
||
|
||
|
||
|
||
} catch (Exception ex) {
|
||
|
||
|
||
|
||
ex.printStackTrace();
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>输出结果如下:</p>
|
||
|
||
<pre><code>9223372036854775808
|
||
|
||
|
||
|
||
java.lang.ArithmeticException: BigInteger out of long range
|
||
|
||
|
||
|
||
at java.math.BigInteger.longValueExact(BigInteger.java:4632)
|
||
|
||
|
||
|
||
at org.geekbang.time.commonmistakes.numeralcalculations.demo3.CommonMistakesApplication.right1(CommonMistakesApplication.java:37)
|
||
|
||
|
||
|
||
at org.geekbang.time.commonmistakes.numeralcalculations.demo3.CommonMistakesApplication.main(CommonMistakesApplication.java:11)
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>可以看到,通过 BigInteger 对 Long 的最大值加 1 一点问题都没有,当尝试把结果转换为 Long 类型时,则会提示 BigInteger out of long range。</p>
|
||
|
||
<h2>重点回顾</h2>
|
||
|
||
<p>今天,我与你分享了浮点数的表示、计算、舍入和格式化、溢出等涉及的一些坑。</p>
|
||
|
||
<p>第一,切记,要精确表示浮点数应该使用 BigDecimal。并且,使用 BigDecimal 的 Double 入参的构造方法同样存在精度丢失问题,应该使用 String 入参的构造方法或者 BigDecimal.valueOf 方法来初始化。</p>
|
||
|
||
<p>第二,对浮点数做精确计算,参与计算的各种数值应该始终使用 BigDecimal,所有的计算都要通过 BigDecimal 的方法进行,切勿只是让 BigDecimal 来走过场。任何一个环节出现精度损失,最后的计算结果可能都会出现误差。</p>
|
||
|
||
<p>第三,对于浮点数的格式化,如果使用 String.format 的话,需要认识到它使用的是四舍五入,可以考虑使用 DecimalFormat 来明确指定舍入方式。但考虑到精度问题,我更建议使用 BigDecimal 来表示浮点数,并使用其 setScale 方法指定舍入的位数和方式。</p>
|
||
|
||
<p>第四,进行数值运算时要小心溢出问题,虽然溢出后不会出现异常,但得到的计算结果是完全错误的。我们考虑使用 Math.xxxExact 方法来进行运算,在溢出时能抛出异常,更建议对于可能会出现溢出的大数运算使用 BigInteger 类。</p>
|
||
|
||
<p>总之,对于金融、科学计算等场景,请尽可能使用 BigDecimal 和 BigInteger,避免由精度和溢出问题引发难以发现,但影响重大的 Bug。</p>
|
||
|
||
<p>今天用到的代码,我都放在了 GitHub 上,你可以点击这个链接查看。</p>
|
||
|
||
<h2>思考与讨论</h2>
|
||
|
||
<p>BigDecimal提供了 8 种舍入模式,你能通过一些例子说说它们的区别吗?</p>
|
||
|
||
<p>数据库(比如 MySQL)中的浮点数和整型数字,你知道应该怎样定义吗?又如何实现浮点数的准确计算呢?</p>
|
||
|
||
<p>针对数值运算,你还遇到过什么坑吗?我是朱晔,欢迎在评论区与我留言分享你的想法,也欢迎你把这篇文章分享给你的朋友或同事,一起交流。</p>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div>
|
||
|
||
<div style="float: left">
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/08 判等问题:程序里如何确定你就是你?.md.html">上一页</a>
|
||
|
||
</div>
|
||
|
||
<div style="float: right">
|
||
|
||
<a href="/专栏/Java 业务开发常见错误 100 例/10 集合类:坑满地的List列表操作.md.html">下一页</a>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
|
||
|
||
</div>
|
||
|
||
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"709970254ed33d60","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
|
||
|
||
</body>
|
||
|
||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||
|
||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
|
||
|
||
<script>
|
||
|
||
window.dataLayer = window.dataLayer || [];
|
||
|
||
|
||
|
||
function gtag() {
|
||
|
||
dataLayer.push(arguments);
|
||
|
||
}
|
||
|
||
|
||
|
||
gtag('js', new Date());
|
||
|
||
gtag('config', 'G-NPSEEVD756');
|
||
|
||
var path = window.location.pathname
|
||
|
||
var cookie = getCookie("lastPath");
|
||
|
||
console.log(path)
|
||
|
||
if (path.replace("/", "") === "") {
|
||
|
||
if (cookie.replace("/", "") !== "") {
|
||
|
||
console.log(cookie)
|
||
|
||
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
|
||
|
||
}
|
||
|
||
} else {
|
||
|
||
setCookie("lastPath", path)
|
||
|
||
}
|
||
|
||
|
||
|
||
function setCookie(cname, cvalue) {
|
||
|
||
var d = new Date();
|
||
|
||
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
|
||
|
||
var expires = "expires=" + d.toGMTString();
|
||
|
||
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
|
||
|
||
}
|
||
|
||
|
||
|
||
function getCookie(cname) {
|
||
|
||
var name = cname + "=";
|
||
|
||
var ca = document.cookie.split(';');
|
||
|
||
for (var i = 0; i < ca.length; i++) {
|
||
|
||
var c = ca[i].trim();
|
||
|
||
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
|
||
|
||
}
|
||
|
||
return "";
|
||
|
||
}
|
||
|
||
|
||
|
||
</script>
|
||
|
||
|
||
|
||
</html>
|
||
|