1. 整数拓展

一、 Java中整数的进制

  • 二进制,前缀为0b或0B,如 0b101 = 1 x 22 + 0x2 + 1 = 5
  • 十进制,正常数字
  • 八进制,加 0, 如 0101 = 1 x 82 + 1 = 65
  • 十六进制, 加0x,如 0x101 = 1 x 162 + 1 = 257

所以二进制里只有1,0两数字,八进制则是 0-7 8个数字,十六进制就是0-9+A-F

浮点数拓展:银行业务怎么表示钱?

float,double的底层存储机制,存储的长度是有限的,离散的,会有舍入误差,只能跟真实值接近但不等于真实值,只能是个约数。

所以银行业务不能用float表示,要精确计算且比较,最好用 Java已经写好的 Bigdecimal 这个大数据类,是个数学工具类。

至于为什么二进制无法精确表示十进制的小数 1/10,你只要知道十进制也同样无法精确表示 1/3 即可。

进一步了解浮点数不能用于精确计算原因

1. 案例

  • 案例一

在java中float赋值给double,会产生精度问题。

float a = 2.1f;
double b = 3.3;
b = a;System.out.println(b);

输出为2.0999999046325684。

  • 案例二
public class Test{    
    public static void main(String args[]){        
        System.out.println(0.05+0.01);        
        System.out.println(1.0-0.42);        
        System.out.println(4.015*100);        
        System.out.println(123.3/100);    
    }
}

结果:

0.060000000000000005
0.580000000000000140
1.49999999999994
1.2329999999999999

2、小数的二进制表示问题

首先我们要搞清楚下面两个问题:

  • A、十进制整数如何转化为二进制数

算法很简单。举个例子,11表示成二进制数:

11/2=5 余 1
5/2=2 余 1
2/2=1 余 0
1/2=0 余 1
0结束
11二进制表示为(从下往上):1011

这里提一点:只要遇到除以后的结果为0了就结束了,大家想一想,所有的整数除以2是不是一定能够最终得到0。换句话说,所有的整数转变为二进制数的算法会不会无限循环下去呢?绝对不会,整数永远可以用二进制精确表示 ,但小数就不一定了。

  • B、十进制小数如何转化为二进制数

算法是乘以2直到没有了小数为止。举个例子,0.9表示成二进制数

0.9*2=1.8 取整数部分 1
0.8(1.8的小数部分)*2=1.6 取整数部分 1
0.6*2=1.2 取整数部分 1
0.2*2=0.4 取整数部分 0
0.4*2=0.8 取整数部分 0
0.8*2=1.6 取整数部分 1
0.6*2=1.2 取整数部分 0
.........
0.9二进制表示为(从上往下): 1100100100100......

注意:上面的计算过程循环了,也就是说*2永远不可能消灭小数部分,这样算法将无限下去。很显然,小数的二进制表示有时是不可能精确的 。其实道理很简单,十进制系统中能不能准确表示出1/3呢?同样二进制系统也无法准确表示1/10。这也就解释了为什么浮点型减法出现了”减不尽”的精度丢失问题。

3、float型在内存中的存储

众所周知、 Java 的float型在内存中占4个字节。float的32个二进制位结构如下
float内存存储结构

4bytes313029~2322~0
表示实数符号位指数符号位指数位有效数位

其中符号位1表示正,0表示负。有效位数位24位,其中一位是实数符号位。
将一个float型转化为内存存储格式的步骤为:

  • 先将这个实数的绝对值化为二进制格式,注意实数的整数部分和小数部分的二进制方法在上面已经探讨过了。
  • 将这个二进制格式实数的小数点左移或右移n位,直到小数点移动到第一个有效数字的右边。
  • 从小数点右边第一位开始数出二十三位数字放入第22到第0位。
  • 如果实数是正的,则在第31位放入“0”,否则放入“1”。
  • 如果n 是左移得到的,说明指数是正的,第30位放入“1”。如果n是右移得到的或n=0,则第30位放入“0”。
  • 如果n是左移得到的,则将n减去1后化为二进制,并在左边加“0”补足七位,放入第29到第23位。如果n是右移得到的或n=0,则将n化为二进制后在左边加“0”补足七位,再各位求反,再放入第29到第23位。

举例说明: 11.9的内存存储格式

  • 将11.9化为二进制后大约是” 1011. 1110011001100110011001100…”。
  • 将小数点左移三位到第一个有效位右侧: “1. 011 11100110011001100110 “。 保证有效位数24位,右侧多余的截取(误差在这里产生了 )。
  • 这已经有了二十四位有效数字,将最左边一位“1”去掉,得到“ 011 11100110011001100110 ”共23bit。将它放入float存储结构的第22到第0位。
  • 因为11.9是正数,因此在第31位实数符号位放入“0”。
  • 由于我们把小数点左移,因此在第30位指数符号位放入“1”。
  • 因为我们是把小数点左移3位,因此将3减去1得2,化为二进制,并补足7位得到0000010,放入第29到第23位。
  • 最后表示11.9为: 0 1 0000010 011 11100110011001100110

再举一个例子:0.2356的内存存储格式

  • 将0.2356化为二进制后大约是0.00111100010100000100100000。
  • 将小数点右移三位得到1.11100010100000100100000。
  • 从小数点右边数出二十三位有效数字,即11100010100000100100000放
  • 入第22到第0位。
  • 由于0.2356是正的,所以在第31位放入“0”。
  • 由于我们把小数点右移了,所以在第30位放入“0”。
  • 因为小数点被右移了3位,所以将3化为二进制,在左边补“0”补足七
  • 位,得到0000011,各位取反,得到1111100,放入第29到第23位。
  • 最后表示0.2356为:0 0 1111100 11100010100000100100000

将一个内存存储的float二进制格式转化为十进制的步骤:

  • 将第22位到第0位的二进制数写出来,在最左边补一位“1”,得到二十四位有效数字。将小数点点在最左边那个“1”的右边。
  • 取出第29到第23位所表示的值n。当30位是“0”时将n各位求反。当30位是“1”时将n增1。
  • 将小数点左移n位(当30位是“0”时)或右移n位(当30位是“1”时),得到一个二进制表示的实数。
  • 将这个二进制实数化为十进制,并根据第31位是“0”还是“1”加上正号或负号即可。

4、浮点型的减法运算

  • 0操作数的检查;
    如果判断两个需要加减的浮点数有一个为0,即可得知运算结果而没有必要再进行有序的一些列操作。
  • 比较阶码(指数位)大小并完成对阶;
  • 两浮点数进行加减,首先要看两数的指数位是否相同,即小数点位置是否对齐。若两数 指数位 相同,表示小数点是对齐的,就可以进行尾数的加减运算。反之,若两数阶码不同,表示小数点位置没有对齐,此时必须使两数的阶码相同,这个过程叫做对阶 。
  • 如何对 阶(假设两浮点数的指数位为 Ex 和 Ey ):通过尾数的移位以改变 Ex 或 Ey ,使之相等。 由 于浮点表示的数多是规格化的,尾数左移会引起最高有位的丢失,造成很大误差;而尾数右移虽引起最低有效位的丢失,但造成的误差较小,因此,对阶操作规定使尾数右移,尾数右移后使阶码作相应增加,其数值保持不变。很显然,一个增加后的阶码与另一个相等,所增加的阶码一定是小阶。因此在对阶时,总是使小阶向大阶看齐 ,即小阶的尾数向右移位 ( 相当于小数点左移 ) ,每右移一位,其阶码加 1 ,直到两数的阶码相等为止,右移的位数等于阶差 △ E 。
  • 尾数(有效数位)进行加或减运算;
  • 结果规格化并进行舍入处理。

    5、精度问题解决方法

  • 1、解决浮点数精确计算有误差的方法

在《Effective Java》这本书中也提到这个原则,float和double只能用来做科学计算或者是工程计算,在商业计算中我们要用java.math.BigDecimal。使用BigDecimal并且一定要用String来够造。

BigDecimal用哪个构造函数?

BigDecimal(double val) 
BigDecimal(String val)   

上面的API简要描述相当的明确,而且通常情况下,上面的那一个使用起来要方便一些。我们可能想都不想就用上了,会有什么问题呢?等到出了问题的时候,才发现参数是double的构造方法的详细说明中有这么一段:

Note: the results of this constructor can be somewhat unpredictable. One might assume that new BigDecimal(.1) is exactly equal to .1, but it is actually equal to .1000000000000000055511151231257827021181583404541015625. This is so because .1 cannot be represented exactly as a double (or, for that matter, as a binary fraction of any finite length). Thus, the long value that is being passed in to the constructor is not exactly equal to .1, appearances nonwithstanding.
The (String) constructor, on the other hand, is perfectly predictable: new BigDecimal(“.1”) is exactly equal to .1, as one would expect. Therefore, it is generally recommended that the (String) constructor be used in preference to this one.
原来我们如果需要精确计算,非要用String来够造BigDecimal不可!

  • 2、bigdecimal比等方法

如浮点类型一样, BigDecimal 也有一些令人奇怪的行为。尤其在使用 equals() 方法来检测数值之间是否相等时要小心。 equals() 方法认为,两个表示同一个数但换算值不同(例如, 100.00 和 100.000 )的 BigDecimal 值是不相等的。然而, compareTo() 方法会认为这两个数是相等的,所以在从数值上比较两个 BigDecimal 值时,应该使用 compareTo() 而不是 equals()

  • 3、简化bigdecimal计算的小工具类

如果我们要做一个加法运算,需要先将两个浮点数转为String,然后够造成BigDecimal,在其中一个上调用add方法,传入另一个作为参数,然后把运算的结果(BigDecimal)再转换为浮点数。你能够忍受这么烦琐的过程吗?网上提供的工具类Arith来简化操作。它提供以下静态方法,包括加减乘除和四舍五入:

public   static   double   add(double   v1,double   v2)   
public   static   double   sub(double   v1,double   v2)   
public   static   double   mul(double   v1,double   v2)   
public   static   double   div(double   v1,double   v2)   
public   static   double   div(double   v1,double   v2,int   scale)   
public   static   double   round(double   v,int   scale)  
import java.math.BigDecimal;    
/**  
 * 进行BigDecimal对象的加减乘除,四舍五入等运算的工具类  
 * @author ameyume  
 *  
 */  
public class Arith {    
    /**   
    * 由于Java的简单类型不能够精确的对浮点数进行运算,这个工具类提供精   
    * 确的浮点数运算,包括加减乘除和四舍五入。   
    */    
    //默认除法运算精度    
    private static final int DEF_DIV_SCALE = 10;    
    //这个类不能实例化    
    private Arith(){    
    }    
    /**   
     * 提供精确的加法运算。   
     * @param v1 被加数   
     * @param v2 加数   
     * @return 两个参数的和   
     */    
    public static double add(double v1,double v2){    
        BigDecimal b1 = new BigDecimal(Double.toString(v1));    
        BigDecimal b2 = new BigDecimal(Double.toString(v2));    
        return b1.add(b2).doubleValue();    
    }    
    /**   
     * 提供精确的减法运算。   
     * @param v1 被减数   
     * @param v2 减数   
     * @return 两个参数的差   
     */    
    public static double sub(double v1,double v2){    
        BigDecimal b1 = new BigDecimal(Double.toString(v1));    
        BigDecimal b2 = new BigDecimal(Double.toString(v2));    
        return b1.subtract(b2).doubleValue();    
    }    
    /**   
     * 提供精确的乘法运算。   
     * @param v1 被乘数   
     * @param v2 乘数   
     * @return 两个参数的积   
     */    
    public static double mul(double v1,double v2){    
        BigDecimal b1 = new BigDecimal(Double.toString(v1));    
        BigDecimal b2 = new BigDecimal(Double.toString(v2));    
        return b1.multiply(b2).doubleValue();    
    }    
    /**   
     * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到   
     * 小数点以后10位,以后的数字四舍五入。   
     * @param v1 被除数   
     * @param v2 除数   
     * @return 两个参数的商   
     */    
    public static double div(double v1,double v2){    
        return div(v1,v2,DEF_DIV_SCALE);    
    }    
    /**   
     * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指   
     * 定精度,以后的数字四舍五入。   
     * @param v1 被除数   
     * @param v2 除数   
     * @param scale 表示表示需要精确到小数点以后几位。   
     * @return 两个参数的商   
     */    
    public static double div(double v1,double v2,int scale){    
        if(scale<0){    
            throw new IllegalArgumentException(    
                "The scale must be a positive integer or zero");    
        }    
        BigDecimal b1 = new BigDecimal(Double.toString(v1));    
        BigDecimal b2 = new BigDecimal(Double.toString(v2));    
        return b1.divide(b2,scale,BigDecimal.ROUND_HALF_UP).doubleValue();    
    }    
    /**   
     * 提供精确的小数位四舍五入处理。   
     * @param v 需要四舍五入的数字   
     * @param scale 小数点后保留几位   
     * @return 四舍五入后的结果   
     */    
    public static double round(double v,int scale){    
        if(scale<0){    
            throw new IllegalArgumentException(    
                "The scale must be a positive integer or zero");    
        }    
        BigDecimal b = new BigDecimal(Double.toString(v));    
        BigDecimal one = new BigDecimal("1");    
        return b.divide(one,scale,BigDecimal.ROUND_HALF_UP).doubleValue();    
    }    
   /**   
    * 提供精确的类型转换(Float)   
    * @param v 需要被转换的数字   
    * @return 返回转换结果   
    */    
    public static float convertsToFloat(double v){    
        BigDecimal b = new BigDecimal(v);    
        return b.floatValue();    
    }    
    /**   
    * 提供精确的类型转换(Int)不进行四舍五入   
    * @param v 需要被转换的数字   
    * @return 返回转换结果   
    */    
    public static int convertsToInt(double v){    
        BigDecimal b = new BigDecimal(v);    
        return b.intValue();    
    }    
    /**   
    * 提供精确的类型转换(Long)   
    * @param v 需要被转换的数字   
    * @return 返回转换结果   
    */    
    public static long convertsToLong(double v){    
        BigDecimal b = new BigDecimal(v);    
        return b.longValue();    
    }    
    /**   
    * 返回两个数中大的一个值   
    * @param v1 需要被对比的第一个数   
    * @param v2 需要被对比的第二个数   
    * @return 返回两个数中大的一个值   
    */    
    public static double returnMax(double v1,double v2){    
        BigDecimal b1 = new BigDecimal(v1);    
        BigDecimal b2 = new BigDecimal(v2);    
        return b1.max(b2).doubleValue();    
    }    
    /**   
    * 返回两个数中小的一个值   
    * @param v1 需要被对比的第一个数   
    * @param v2 需要被对比的第二个数   
    * @return 返回两个数中小的一个值   
    */    
    public static double returnMin(double v1,double v2){    
        BigDecimal b1 = new BigDecimal(v1);    
        BigDecimal b2 = new BigDecimal(v2);    
        return b1.min(b2).doubleValue();    
    }    
    /**   
    * 精确对比两个数字   
    * @param v1 需要被对比的第一个数   
    * @param v2 需要被对比的第二个数   
    * @return 如果两个数一样则返回0,如果第一个数比第二个数大则返回1,反之返回-1   
    */    
        public static int compareTo(double v1,double v2){    
        BigDecimal b1 = new BigDecimal(v1);    
        BigDecimal b2 = new BigDecimal(v2);    
        return b1.compareTo(b2);    
    }   
}
  • bigdecimal构造函数使用不当带来异常

BigDecimal其中一个构造函数以双精度浮点数作为输入,另一个以整数和换算因子作为输入,还有一个以小数的 String 表示作为输入。要小心使用 BigDecimal(double) 构造函数,因为如果不了解它,会在计算过程中产生舍入误差。请使用基于整数或 String 的构造函数。

原文地址:

参考:

2. 字符拓展

所有字符本质还是数字,这涉及编码问题,所有的字符都在 Unicode 编码表中有相应的数字表示。

因为字符char占2B,所以Unicode表也占2B,大小范围是 0-65535

更多Unicode相关的资料
  • Java 语言使用的字符集是 Unicode 字符集;
  • 而 Java 使用的编码方式则为 UTF-16
  • 同时,在 Java 中,char 类型用于表示 UTF-16 编码方式下 Unicode 编码的一个代码单元

经过长时间的发展,有人意识到,世界上不同各有一套相互不认的编码,能否有统一标准展现世界上所有文字,而正是出于这个目的,Unicode 诞生了。

在 Unicode 标准中,仅仅为每个字符分配了一个唯一的字符编号(代码点,Code Point),对于这个数字对应的二进制串如何存储并没有规定

值得注意的是:这个数字采用 U+ 紧跟着十六进制数表示。例如:U+56DE 代表汉字

  • Unicode 字符集仅规定了每个字符的码点(唯一编码ID)
  • Unicode 字符集有多种编码方案:UTF-8、UTF-16、UTF-32等
  • Java 使用的字符集为 Unicode 字符集,编码方案为 UTF-16
  • Java 中的 char 占 2 个字节
  • Java 中 char 代表 UTF-16 编码方案下 Unicode 编码的一个代码单元(Code Unit)
  • Java 中一个字符可能占 1 个 char,也可能占 2 个 char

Note! 引用别人一句话强烈建议不要在程序中使用 char 类型,如果可以,请使用 String

更多Unicode相关的内容,阅读:https://blog.csdn.net/qq_37164975/article/details/109173478

更多参考资料:

转义字符

在Java字符常量中,反斜杠\是一个特殊的字符,被称为转义字符。而在Java语言中定义了一些字母前加\来表示特殊含义的字符, 如\0,\t,\n等, 称为转义字符(Escape Character).

转义就是指转换该字符的原本意义,从而变成另外的意义。
所有的ASCII码都可以用\加数字(一般是8进制数字)来表示。

常用转义字符表:(ASCII十进制码值)

Java基础语法04 - 数据类型扩展

//Java 中常见的特殊字符和控制字符
//特殊字符
\" : 双引号
\' : 单引号
\\ : 反斜线
\' : 单引号
    
//控制字符
\r 回车
\n 换行
\f 走纸换页
\t 横向跳格
\b 退格

3. 布尔拓展

boolean flag = true ;
if(flag == ture) {} //新手行为
if(flag) {} //老手行为

4. 测试:

a. 新建Demo3.java

public class Demo03 {
    public static void main(String[] args) {
        //1. 整数拓展
        /*
        二进制 0b开头
        八进制 0开头
        十六进制 0x开头
         */
        int num = 10 ;
        int num1 = 0b11001100 ; //2进制,2^7 + 2^6 + 2^3 + 2^2 = 128 + 64 + 8 + 4 = 204
        int num2 = 010 ; //8进制,1x8 = 8
        int num3 = 0x10 ; //16进制,1x16 = 16
        System.out.println("num " + num + "\r");
        System.out.println("num " + num1 + "\r");
        System.out.println("num " + num2 + "\r");
        System.out.println("num " + num3 + "\r");
        System.out.println("============== " + "\r");
        //2 浮点拓展
        /*
        float
        double
         */
        float f = 0.1f ; //0.1
        double d = 1.0/10 ; //0.1
        System.out.println( f==d); //结果为false

        float f2 = 2343409213f ;
        float d2 = f2 + 1 ;
        System.out.println(f2 == d2); //结果是ture, 明明是+1,结果却是true
        //所以用 float 进行精度计算很容易出问题。
        //银行业务中用Java预设好的类 Bigdecimal ;
        System.out.println("============== " + "\r");

        //3.字符拓展
        /*
        所有的字符本质还是数字,是存储在编码表中的码点,Java中采用的Unicode-16编码表
        // U0000 - UFFFF ; 16进制
         */
        char c1 = 'a' ;
        char c2 = '中' ;
        System.out.println(c1);
        System.out.println((int)c1);
        System.out.println(c2);
        System.out.println((int)c2);
        System.out.println("============== " + "\r");

        //转义符
        /*
        \t 制表符
        \n 换行符
        \r 不分段换行,相当于Shift+Enter的效果
         */
        System.out.println("Hello\rWorld!"); //显示结果为World!
        System.out.println("Hello\nWorld!");
        System.out.println("Hello\tWorld!");
        System.out.println("============== " + "\r");

        String s1 = new String("Hello World!");
        String s2 = new String("Hello World!");
        System.out.println(s1 == s2); //结果为 false
        String s3 = "Hello World!";
        String s4 = "Hello World!";
        System.out.println(s3==s4); //结果为 true


    }
}

b.测试结果:

num 10
num 204
num 8
num 16
============== 
false
true
============== 
a
97
中
20013
============== 
World!
Hello
World!
Hello    World!
============== 
false
true
文章目录