装饰公司排名百度seo有用吗
牛顿-拉夫逊迭代法
牛顿迭代法是一种快速求解方程根的方法,它的迭代原理如下:
选择一个初始点(x0, f(x0) ),通过该点做切线。切线与X轴的交点记为x1,做为下一次迭代的点,再在(x1, f(x1) )处做切线,依次类推,直道求解满足一定的精度即可。
我们以求解根的该曲线的近似解为例,其动态求解过程如下:
在进行叙述之前,需要对求近似解的原因做几点说明:
- 很多方程是无法准确求解的,如我们知道 ex=2 的解为,x= ln2. 但是你无法表达出ln2
- 计算机的硬件资源有限, 无法准确将数字完全的表达出来,而且有的时候完全没有必要
- 计算机是用二进制表达数据,有些数据用二进制可以准确表示,但是有的数据二进制格式只能近似表达
那么,在牛顿法在数学上该如何表达呢?
过曲线上一点做切线,求切线与X轴的交点,需要用到导数
假设切线的方程为:
y = k x + b y=kx+b y=kx+b
那么过( xn, f(xn))点的切线为
y − f ( x n ) = f ′ ( x n ) ( x − x n ) y-f(x_n)=f'(x_n)(x-x_n) y−f(xn)=f′(xn)(x−xn)
令 y=0, 我们有
x n + 1 ∗ f ′ ( x n ) = f ′ ( x n ) ∗ x n − f ( x n ) x_{n+1}*f'(x_n)= f'(x_n)*x_n-f(x_n) xn+1∗f′(xn)=f′(xn)∗xn−f(xn)
有
x n + 1 = x n − f ( x n ) f ′ ( x n ) x_{n+1}=x_n-\frac{f(x_n)}{f'(x_n)} xn+1=xn−f′(xn)f(xn)
停止迭代方法有很多。常用的方法有设定迭代次数和相对迭代相对误差小于一定精度,根据自己的情况选择合适的方法。
后续我们将以设计一个定点除法器为例,手把手带大家动手实践如何使用牛顿迭代法。不过在进入设计之前,很有必要了解计算机中数字的表示方法。
浮点和定点问题
计算机的首要工作是计算,计算需要则需要处理数据。那么计算机是如何表示数据呢?通常来说,在计算机领域编程中,你通常会发现数字的类型通常有两种:整数型和浮点型。这恰恰对应了两种数据的表示方式。
浮点
浮点方法表示数据常用在科学计数法,尤其是在当今AI的训练中。例如:
1.6E+19 =1.6 x 1019
9E-15 =9x10-15
由于二进制的特质,任何数据在都可以表示成如下形式:
1. x x x x ∗ 2 y y y y y y 1.xxxx *2^{yyyyyy} 1.xxxx∗2yyyyyy
而浮点之所以叫浮点,在运算前后数据的1后面的小数点的位置是会改变的
单精度浮点数与10进制数的转换关系如下:
31位:符号位,1代表负数,0代表正数
30~23位 :指数位
22~0位: 小数位
bias 为偏置= 2指数位-1-1, 在单精度浮点数下,bias为 2^7-1=127
例如
符号位:1,该数据为负数
指数位:1000001(2)=129(10)
小数为:01000000… =0.25
所以该数据为
( − 1 ) S ∗ ( 1 + 0.25 ) ∗ 2 129 − 127 = − 5.0 (-1)^S *(1+0.25)*2^{129-127}=-5.0 (−1)S∗(1+0.25)∗2129−127=−5.0
定点
定点格式的表示
在计算机中int类型是一种特殊的定点数据的表示方式,它直接忽略了小数位。然而在实际一定精度运算中,小数位是不能忽略的,那么该如何处理呢?接下来,定点格式提供解决该问题
具体定点数代表的数值以 FN 代为缩写,可由以下公式 4-5 给出,以字长 (Word length)表示定点数的整体长度,简写为wl;符号位的数值以s表示;以 Xi表示除了符号位以外每个位置上的数值;以分数长(Fractional length)表示定 点数中分数位的长度,简写为fl;以整数长(Integer length)表示定点数中整数 位的长度,简写为il
一般来说,定点数在嵌入式系统中更常见,因为使用定点方式进行算术时,尤其是进行乘除法时,资源消耗和延迟远比采用浮点方法更好。需要说明的是,其实在硬件并不能识别小数点,它默认数据都为整数,那为什么能够表示小数呢?这是设计者凭空想象出来的。计算机处理的结果经过移位操作,就可以得到设计者规定的定点模式。
定点格式的转换
- 数据的扩位与截位
一般来说,数据的扩位不会造成精度的影响; 数据的截位会造成误差
扩展
无符号数据(以8位整数位,8位小数位,即S0I8F8, Q8.8格式为例)
Q8.8–>Q16.16 , 在原整数位最高位加入8bit,在小数位最低位再加8bit
有符号数据(S1I8F8为例)
Q8.8–>Q16.16–>如果原来的数为正数,在原最高整数位补8个零,小数位再补8bit;为负数的话,则需要在原最高整数位补8个1,小数再补8bit
截位
无符号
Q16.16–>Q8.8–> 截掉整数最高8位和低小数最低8位,整数需要判断是否进行溢出处理
有符号
Q16.16->Q8.8–> 小数位直接截掉即可,整数位在截取的时候需要判断是否溢出,如果溢出进行饱和处理,不然确保符号正确的情况下,截掉整数最高8位
比较
浮点
-
指数部分动态,范围很大
-
精度灵活,开发便捷(无需处理溢出和缩放问题)
-
运算起来复杂(需要对齐指数、规格化、舍入等)
-
需要使用浮点运算单元,能耗较大
定点
-
整数部分和小数部分固定,表示范围小
-
运算起来简单(移位,加减)
-
无需硬件支持,适合低功耗设备
-
存在确定性误差,容易溢出
Python
案例:使用牛顿–拉夫逊迭代法,对除法硬件进行描述
输入(dividend, divisor)数据均为S0I8F0, 输出(quotient)为S0I8F8
思路分析:
q u o t i e n t (商) = d i v i d e n d ( 被除数 ) / d i v i s o r ( 除数 ) quotient(商)=dividend(被除数)/divisor(除数) quotient(商)=dividend(被除数)/divisor(除数)
可以将除法转换为乘法
q u o t i e n t = d i v i d e n d ∗ 1 d i v i s o r quotient= dividend*\frac{1}{divisor} quotient=dividend∗divisor1
所以,我们可以使用牛顿迭代法,快速求出1/divisor的近似值,然后将其与dividend相乘,即可以获得结果
因为
$$
1/\frac{1}{divisor}=divisor
令 令 令
f(x)= \frac{1}{x}-divisor
$$
我们求f(x)=0的根即可
根据我们前面推到的公式有:
x n e x t = x c u r r e n t − f ( x c u r r n e t ) f ′ ( x c u r r e n t ) = x c u r r e n t − x c u r r e n t − 1 − d i v i s o r x c u r r e n t − 2 = x c u r r e n t + x c u r r e n t 2 ( x c u r r e n t − 1 − d i v i s o r ) x_{next}=x_{current}-\frac{f(x_{currnet})}{f'(x_{current})} =x_{current}-\frac{x^{-1}_{current}-divisor}{x^{-2}_{current}} =x_{current}+x^2_{current}(x^{-1}_{current}-divisor) xnext=xcurrent−f′(xcurrent)f(xcurrnet)=xcurrent−xcurrent−2xcurrent−1−divisor=xcurrent+xcurrent2(xcurrent−1−divisor)
$$
=2x_{current}-divisorx^2_{current}=x_{current}(2-divisorx_{current})
$$
代码
import math#四舍五入
def round_half_up(x):return int(math.floor(x + 0.5))#牛顿迭代法
def newton_raphson_division(dividend, divisor, iterations=4):if divisor == 0:return Nonex = 1.0 / max(divisor, 1e-9) #给定初始值for _ in range(iterations):x *= 2 - divisor * xquotient = dividend * xreturn round_half_up(quotient * (1 << 8))#满精度除法,用于比较
def full_precision_divide(dividend, divisor):if divisor == 0:return Nonequotient = (dividend / divisor) * (1 << 8)return round_half_up(quotient)def main():methods = [#我实验了很多除法器,此处只使用牛顿迭代法#('direct_division', dm.direct_division),#('restoring_remainder', dm.restoring_remainder_division),#('non_restoring_division', dm.non_restoring_division),#('lut_division', dm.lut_division),#('binary_bit_method', dm.binary_bit_division),('newton_raphson_division', newton_raphson_division),]error_counts = {name: 0 for name, _ in methods}total_cases = 0for dividend in range(256):for divisor in range(1, 256):total_cases += 1expected =full_precision_divide(dividend, divisor)for name, method in methods:result = method(dividend, divisor)if result != expected:error_counts[name] += 1print(f"Mismatch: {name} ({dividend}/{divisor}) Got: {result} Expected: {expected}")print("\nError Summary:")for name, count in error_counts.items():print(f"{name}: {count} errors ({(count/total_cases)*100:.2f}%)")if __name__ == "__main__":main()
结果
错误率为0%
Verilog
软件语言和硬件语言区别在两个例子中,体现的就会非常明显:软件(如python)直接调用的专用浮点处理模块,因而实现起来很简答,因为复杂的部分,做硬件的人已经解决掉了。而硬件语言(Verilog)则需要考虑硬件的实际情况,对位宽精度进行处理。此外在硬件中,是没有小数的概念的,小数点的位置完全由人为控制。
除法器
`timescale 1ns / 1psmodule Newton_Raphson_Division (input [7:0] dividend, // 被除数(Q8.0)input [7:0] divisor, // 除数(Q8.0)output reg [15:0] quotient // 商(Q8.8)
);// 参数定义
localparam Q_FORMAT = 16; // 迭代中间值用Q16.16格式
localparam ITERATIONS = 4; // 迭代次数// 牛顿迭代核心计算
reg [31:0] x; // Q16.16格式的倒数
reg [47:0]product; //Q24.24格式
reg [47:0]term;
reg [79:0]x_temp;
integer i;always @(*) begin// 初始值:1/divisor的近似值(查表简化版)x = 32'hffff_ffff / {divisor, 16'h0000}; // 近似计算// 四次牛顿迭代for (i=0; i<ITERATIONS; i=i+1) beginproduct = (divisor<<8)* x; // (Q8.0-->Q8.8)* Q16.16 = Q24.24term = 48'h200_0000-product; //2 - D*x, 定点格式需要对齐 Q24.24x_temp = x * term; // x = x * term = Q40.40if(x_temp[79:56]!=0) //x_temp的高整数位溢出x={16'hffff,x_temp[39:24]}; //截取Q16.16elsex=x_temp[55:24];end
end// 最终乘法与格式转换
reg [39:0] final_product; always @(*) beginif(divisor==0)quotient=16'hffff;else if (divisor==1)quotient=dividend<<8;else beginfinal_product = dividend * x; // 实际乘法,Q8.0 * Q16.16 = Q24.16//quotient = final_product[31:16] + |final_product[15:0]; // 四舍五入到Q8.8quotient=final_product[23:8]+(final_product[7]?1:0);end
endendmodule
Testbench
`timescale 1ns / 1psmodule div_test();reg [7:0] dividend;
reg [7:0]divisor;
wire [15:0]quotient;initial begin#5;
dividend=8'hFF;
divisor =8'h1;#10;
dividend=8'h03;
divisor =8'h0A;#10;
dividend=8'h08;
divisor =8'h03;#10;
dividend = 8'd145;
divisor = 8'd252;#10;
dividend = 8'd255;
divisor = 8'd239;
endNewton_Raphson_Division uut(.dividend(dividend), // 被除数(S0I8F0).divisor(divisor), // 除数(S0I8F0).quotient(quotient) // 商(S0I8F8));endmodule
结果
- 被除数为ff(255) ,除数为01(1) , 商为ff00,高两位16进制数代表整数,低两位16进制数,结果为255.00
- 被除数为03(3) ,除数为0a(10), 商为004d, 即0.30078125 , 真实值为0.3333333
- 被除数为08(8) ,除数为03(03), 商为02ab, 即2.66796875 , 真实值为2.6666666
- 被除数为91(145),除数为fc(252) , 商为0093, 即0.57421875 , 真实值为0.5753968
- 被除数为ff(255) , 除数为ef(239) , 商为0111, 即1. 06640625, 真实值为1.0669456
可以看出,定点计算存在一定误差,但在资源有限下的情况下,采用定点运算可以达到不错精度的效果
,
- 除数为01(1), 商为ff00,高两位16进制数代表整数,低两位16进制数,结果为255.00
- 被除数为03(3), 除数为0a(10), 商为004d, 即0.30078125 , 真实值为0.3333333
- 被除数为08(8),除数为03(03), 商为02ab, 即2.66796875 , 真实值为2.6666666
- 被除数为91(145),除数为fc(252) , 商为0093, 即0.57421875 , 真实值为0.5753968
- 被除数为ff(255), 除数为ef(239) , 商为0111, 即1. 06640625, 真实值为1.0669456
可以看出,定点计算存在一定误差,但在资源有限下的情况下,采用定点运算可以达到不错精度的效果