浮点数运算是否存在缺陷?(浮点数的运算会产生误差,产生的原因是什么?)

yumo6661周前 (07-10)技术文章18

技术背景

在大多数编程语言中,浮点数运算基于 IEEE 754 标准。该标准下,浮点数以二进制表示,本质是一个整数乘以 2 的幂次方。然而,像 0.1(即 1/10)这类分母不是 2 的幂次方的有理数,无法用二进制精确表示。这就导致在进行浮点数运算时,会出现精度误差。

实现步骤

1. 理解浮点数的二进制表示

0.1 为例,在标准的 binary64 格式中,它的十进制表示为
0.1000000000000000055511151231257827021181583404541015625
,十六进制表示为 0x1.999999999999ap-4。这表明计算机实际存储的 0.1 并非精确的 0.1

2. 分析运算误差

当计算 0.1 + 0.2 时,由于 0.10.2 都无法精确表示,它们的和 0.1 + 0.2 会大于精确的 0.3。具体来说,0.10.2 的近似值相加得到的结果为
0.3000000000000000444089209850062616169452667236328125
,而精确的 0.3 实际存储的值为
0.299999999999999988897769753748434595763683319091796875

3. 硬件层面的误差原因

  • 运算误差:从硬件设计角度,大多数浮点运算都会有误差。因为进行浮点计算的硬件只要求单次运算误差小于最低有效位的 0.5 个单位。在浮点除法中,常用乘法求倒数的方式计算商,如 Z = X * (1/Y)。除法是迭代计算的,每次循环计算商的一些位,直到达到所需精度(误差小于最低有效位的 1 个单位)。商选择表(QST)中的倒数都是实际倒数的近似值,会引入误差。
  • 截断误差:IEEE - 754 允许对最终结果进行不同模式的截断,如截断、向零舍入、四舍五入(默认)、向下舍入和向上舍入等。这些方法在单次运算中都会引入小于最低有效位 1 个单位的误差,多次运算后,截断误差会累积。

核心代码

1. 将双精度浮点数转换为二进制表示的 C# 代码

public static string BinaryRepresentation(double value)
{
    long valueInLongType = BitConverter.DoubleToInt64Bits(value);
    string bits = Convert.ToString(valueInLongType, 2);
    string leadingZeros = new string('0', 64 - bits.Length);
    string binaryRepresentation = leadingZeros + bits;

    string sign = binaryRepresentation[0].ToString();
    string exponent = binaryRepresentation.Substring(1, 11);
    string mantissa = binaryRepresentation.Substring(12);

    return string.Format("{0}:{1}:{2}", sign, exponent, mantissa);
}

2. 解决浮点数精度问题的 JavaScript 函数

function floatify(number){
    return parseFloat((number).toFixed(10));
}

function addUp(){
    var number1 = +$("#number1").val();
    var number2 = +$("#number2").val();
    var unexpectedResult = number1 + number2;
    var expectedResult = floatify(number1 + number2);
    $("#unexpectedResult").text(unexpectedResult);
    $("#expectedResult").text(expectedResult);
}
addUp();

3. 自定义加法函数

function add(a, b, precision) {
    var x = Math.pow(10, precision || 2);
    return (Math.round(a * x) + Math.round(b * x)) / x;
}

最佳实践

1. 四舍五入

在显示浮点数之前,使用四舍五入函数将其保留到所需的小数位数。例如,在 JavaScript 中可以使用 toFixed() 方法:

var result = 0.1 + 0.2;
console.log(result.toFixed(2)); // 输出 0.30

2. 比较时使用容差

避免直接使用 == 进行浮点数的相等比较,而是使用容差比较。例如:

function isEqual(x, y, tolerance) {
    return Math.abs(x - y) < tolerance;
}

var x = 0.1 + 0.2;
var y = 0.3;
var tolerance = 0.0000001;
console.log(isEqual(x, y, tolerance)); // 根据容差判断是否相等

3. 使用专门的库

对于对精度要求较高的场景,可以使用 Python 的 decimal 模块或 Java 的 BigDecimal 类,它们以十进制表示数字,能解决大部分二进制浮点数运算的常见问题。例如在 Python 中:

from decimal import Decimal
print(Decimal('0.1') + Decimal('0.2') == Decimal('0.3'))  # 输出 True

常见问题

1. 为什么 0.1 + 0.2 不等于 0.3?

因为 0.10.2 在二进制中无法精确表示,它们的近似值相加后得到的结果与精确的 0.3 存在差异。

2. 如何避免浮点数运算的精度问题?

可以采用四舍五入、使用容差比较、使用专门的库(如 Python 的 decimal 模块、Java 的 BigDecimal 类)等方法。

3. 浮点数运算的误差会累积吗?

会的。由于硬件在单次运算时只保证误差小于最低有效位的 0.5 个单位,多次运算后误差会累积。因此在需要有界误差的计算中,数学家会使用一些方法,如 IEEE - 754 的四舍五入到最接近的偶数位,结合区间算术和不同的舍入模式来预测和纠正舍入误差。

相关文章

一张图认识Python(附基本语法总结)

一张图认识Python(附基本语法总结) 一张图带你了解Python,更快入门,一张图认识Python(附基本语法总结)Python基础语法总结:1.Python标识符在 Python 里,标识符有字...

Python运算符:数学助手,轻松拿咧

Python中的运算符就像是生活中的数学助手,帮助我们快速准确地完成这些计算。比如购物时计算总价、做家务时分配任务等。这篇文章就来详细聊聊Python中的各种运算符,并通过实际代码示例帮助你更好地理解...

Python变量(python变量未定义怎么解决)

以下是关于Python变量的详细介绍及示例,适合初学者理解变量的基本概念和使用方法:一、变量是什么?变量是程序中用于存储数据的容器。每个变量都有名称和值,通过变量名可以访问或修改其存储的值。特点:动态...

int()的“魔术”:Python整数转换到底藏了什么功夫?

到底谁没用过 int() 呢?有时候,你信手拈来——输入个字符串数字,丢进 int(),它就乖乖变成你想要的整数。但你是不是好奇,这玩意到底怎么做到的?别说你一点没怀疑过:“它咋就这么牛呢?能不能转换...

Python教程(四):变量和数据类型简单解释

现在我们已经介绍了如何打印和获取用户输入,是时候深入了解变量和数据类型了 — 这是任何编程语言中最重要的两个概念。今天,我们将解释如何在Python中存储信息并使用不同类型的数据。 今天您将学习什么...