65.9K
CodeProject 正在变化。 阅读更多。
Home

浮点数作为近似值和使用误差

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.82/5 (12投票s)

2017年1月7日

CPOL

4分钟阅读

viewsIcon

36299

downloadIcon

217

浮点数作为近似值,以及理解使用单精度和双精度数据类型时发生的误差

CFloat.vb 是一个用于在VB中处理和解释浮点数的类。

什么是浮点数

计算机使用浮点数来处理带有小数点的实数。在某些情况下,我们想要表示的数字不能完全由浮点二进制数字序列的角色来精确表示。 因此,浮点数的数学运算可能会产生与我们预期略有不同的结果。 例如:在C#,VB,VBA,Python,PHP和Java中计算0.43 + 0.000001 - 0.430001,将不会返回0 !!

存储图

标准IEEE 754-2019 / ISO IEC 60559:2020 旨在组织浮点运算的轮廓。

[Float / Single]数据类型存储在4个字节= 32位中,如下面的二进制存储图所示。 S是信号位,E是指数位,F是尾数位。
尾数也称为有效数字。

单精度数字的二进制格式如下

S EEEE EEEE FFF FFFF FFFF FFFF FFFF FFFF
  内存
十六进制
信号
指数
十六进制
尾数
十六进制
Epsilon
最小正数
1.4E-45
0000 0001 0 00 000 0001
零; +0! 0000 0000 0 00 000 0000
负零; -0! 8000 0000 1 00 000 0000
1! 3F80 0000 0 7F 000 0000
大于1的最小数
1.00000012!
3F80 0001 0 7F 000 0001
不是数字; NaN FFC0 0000 1 FF 400 0000
无穷大; ∞ 0F80 0000 0 FF 000 0000
负无穷大; -∞ FF80 0000 1 FF 000 0000

双精度数字的二进制格式如下所示

S EEEE EEEE EEE FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF

  内存
十六进制
信号
指数
十六进制
尾数
十六进制
Epsilon
最小正数
5E-324
0000 0000 0000 0001 0 000 0 0000 0000 0001
零; +0# 0000 0000 0000 0000 0 000 0 0000 0000 0000
负零; -0# 8000 0000 0000 0000 1 000 0 0000 0000 0000
1# 3FF0 0000 0000 0000 0 3FF 0 0000 0000 0000
大于1的最小数
1.0000000000000002#
3FF0 0000 0000 0001 0 3FF 0 0000 0000 0001
不是数字; NaN FFF8 0000 0000 0000 1 7FF 8 0000 0000 0000
无穷大; ∞ FF00 0000 0000 0000 0 7FF 0 0000 0000 0000
负无穷大; -∞ FFF0 0000 0000 0000 1 7FF 0 0000 0000 0000

由浮点数引起的意外代码运行

由于浮点数是实数的近似值,因此加法和数学运算并不总是能得到精确的结果。

加法测试

//C#
public void TestAdd()
{
    double V = 0;
    V = 0.43 + 1E-06 - 0.430001;
    if (V != 0) {
	    Interaction.MsgBox("It will go here!");
    }
    float f = 0;
    f = 0.43f + 1E-06f - 0.430001f;
    if (f != 0) {
	    Interaction.MsgBox("It will go here!");
    }
}
'VB
Sub TestAdd()
    Dim V As Double
    V = 0.43# + 0.000001# - 0.430001#
    If V <> 0 Then
        MsgBox("It will go here!")
    End If
    Dim f As Single
    f = 0.43! + 0.000001! - 0.430001!
    If f <> 0 Then
        MsgBox("It will go here!")
    End If
End Sub

特殊值

'VB
Sub Test(x As Double)
    If x > 0 Then
        MsgBox("X > 0")
    ElseIf x = 0 Then
        MsgBox("X = 0")
    ElseIf x < 0 Then
        MsgBox("X < 0")
    Else
        MsgBox("This is a possible case! What is the value of X hear?")
        Dim R =
        (Double.NaN = 0) = False AndAlso
        (Double.NaN < 0) = False AndAlso
        (Double.NaN > 0) = False
    End If
End Sub

Sub Test(x As Single)
    If x > 0 Then
        MsgBox("X > 0")
    ElseIf x = 0 Then
        MsgBox("X = 0")
    ElseIf x < 0 Then
        MsgBox("X < 0")
    Else
        MsgBox("This is a possible case! What is the value of X hear?")
        Dim R =
        (Single.NaN = 0) = False AndAlso
        (Single.NaN < 0) = False AndAlso
        (Single.NaN > 0) = False
    End If
End Sub
//C#
public void Test(double x)
{
	if (x > 0) {
		Interaction.MsgBox("X > 0");
	} else if (x == 0) {
		Interaction.MsgBox("X = 0");
	} else if (x < 0) {
		Interaction.MsgBox("X < 0");
	} else {
		Interaction.MsgBox("This is a possible case! What is the value of X hear?");
		dynamic R = (double.NaN == 0) == false && (double.NaN < 0) == false && 
        (double.NaN > 0) == false;
	}
}

public void Test(float x)
{
	if (x > 0) {
		Interaction.MsgBox("X > 0");
	} else if (x == 0) {
		Interaction.MsgBox("X = 0");
	} else if (x < 0) {
		Interaction.MsgBox("X < 0");
	} else {
		Interaction.MsgBox("This is a possible case! What is the value of X hear?");
		dynamic R = (float.NaN == 0) == false && 
		(float.NaN < 0) == false && (float.NaN > 0) == false;
	}
}    

如何从指数和尾数获取浮点数值

以下函数将获取浮点数的double值。浮点数的指数偏差和尾数基数取决于浮点数的类型。 见表。

类型 总位数 指数偏差 尾数基数
半精度 / Float16 16 15 2^10
单精度 / Float 32 127 2^23
双精度浮点型 64 1023 2^52
四精度 / Float128 128 16383 2^112

一个数字的值由以下公式计算得出

指数 <> 0

值 = ±2^(指数 - 指数偏差) * (1 + 尾数 / 尾数基数)

指数 = 0

值 = ±2^(1- 指数偏差) * (尾数 / 尾数基数)

'VB
Function GetDoubleValue(IsNegative As Boolean, Exponent As UInt16,
        Fraction As UInt64, ExponentBias As UInt16, FractionBase As UInt64) As Double
    If Exponent = 0 Then
        If Fraction = 0 Then
            Return If(IsNegative, -0#, 0#)
        End If
        Dim FractionRatio = Fraction / FractionBase
        Return If(IsNegative, -1#, 1#) * (2 ^ (1 - ExponentBias)) * FractionRatio
    Else
        If Exponent = 2 * ExponentBias + 1 Then
            If Fraction = 0 Then
                Return If(IsNegative, Double.NegativeInfinity, Double.PositiveInfinity)
            Else
                Return Double.NaN
            End If
        End If
        Dim FractionRatio = Fraction / FractionBase
        Return If(IsNegative, -1#, 1#) * _
               (2 ^ (CInt(Exponent) - ExponentBias)) * (1# + FractionRatio)
    End If
End Function
//C#
public double GetDoubleValue(bool IsNegative, UInt16 Exponent, UInt64 Fraction, 
UInt16 ExponentBias, UInt64 FractionBase)
{
	if (Exponent == 0) {
		if (Fraction == 0) {
			return IsNegative ? -0.0 : 0.0;
		}
		dynamic FractionRatio = Fraction / FractionBase;
		return IsNegative ? -1.0 : 1.0 * 
               (Math.Pow(2, (1 - ExponentBias))) * FractionRatio;
	} else {
		if (Exponent == 2 * ExponentBias + 1) {
			if (Fraction == 0) {
				return IsNegative ? double.NegativeInfinity : double.PositiveInfinity;
			} else {
				return double.NaN;
			}
		}
		dynamic FractionRatio = Fraction / FractionBase;
		return IsNegative ? -1.0 : 1.0 * 
           (Math.Pow(2, (Convert.ToInt32(Exponent) - ExponentBias))) * 
           (1 + FractionRatio);
	}
}

数据库主键和浮点数

由于浮点数是实数的近似值; 因此,将其用作数据库中的主键不是一个好主意。
例如
如果我们有一个名为Customers的表,其中包含一个数据类型为single的字段id,那么即使数据库中存在记录,引用一行也可能导致没有记录。
当我们插入0.4301作为id时,实际的id会略有不同,因此可能会在数据库updateselect中产生意外的结果。
下面的SQL可能会导致没有记录,这取决于数据库驱动程序以及它如何将数字从十进制转换为浮点(float)类型。

Select Customer From Customers where id = 0.4301

浮点数和编程语言

参考文献

© . All rights reserved.