本帖最后由 tangptr@126.com 于 2022-9-28 23:30 编辑
前言
本文的诞生是源于完成作业的时候搞出来的无聊东西。
计算的不是浮点数,而是定点数。
基本理论
整数的表达方式是从右至左,从10的0次方一直到10的N次方。
小数的表达方式就是从小数点从左至右,从10的-1次方一直到10的-N次方。
比方说十进制的小数1.5表达为二进制的小数就是1.1,因为1.5=20+2-1。设计
由于整数太符合直觉了,所以我这里设计的程序就只管0到1的开区间以内的有限小数了。
浮点数精准度太差,再加上本来就是要算定点数,因此就用整数的方法搞计算了。
参数
我给出了三个参数:Number , Precision , PrecedingZeroes=0 。
Number 参数用于表示小数点后的数字。
Precision 参数表示精确至小数点后几位。
PrecedingZeroes 参数用于表示当小数点后的开头几位为0的时候,有多少个零在那。
流程
在小数点后的计算十分容易,直接除以2就好。但是要把小数部分直接视为整数就有绝对不是除以2的问题了。
举个例子,如果用户想要表达0.5,那他会在Number 参数这里传5,如果把5和2做比较就明显是错误的。
正确的做法是在10的进制空间下以2的商作为除数。
说人话就是:以5的正整数次幂为界,进行四舍五入式的比较。
以0.5为例,Number 是5,是符合>=5的,因此小数点后一位置位。剩下0,结束,故0.5对应二进制是0.1。
以0.4为例,Number 是4,不符合>=5,因此小数点后一位复位。剩下4,继续,故0.4对应二进制是0.0...。(暂时不展开计算)
接下来是如何进位的问题。求小数的时候我们需要向右借位,但是这里是整数,因此只能向左借位了。
向左借位的方法是:被除数乘以10,除数乘以5。注意不论是否置位复位,进行下一次迭代时都必须借位。
继续以0.4为例,由于还有余数,需要进位,因此被除数变成了40,除数变成了25。因为40>=25,所以小数点后两位置位。剩下15,还需要继续,0.4对应二进制就是0.01...。(不展开了)
但是问题还没有完。
如果用户传进来的Number 是25呢?要知道0.25对应的二进制应该是0.01,照这么算它会变成0.11111....。还有,如果传进来的Number 是500呢?0.500本质是0.5,可500的本质不是5啊。而且还有个PrecedingZeroes 参数没有考虑在内呢。
于是就存在了初始化除数的问题。不能把除数直接初始化为5,而是要根据情况来定。
解决方案就是初始化的时候,在5的基础上,乘以10的非负整数次幂来消除长度问题。
以0.25为例,在第一次迭代时它应该和0.50作比较,换句话说就是25应该和50作比较。也就是说因为25大于10的1次幂,除数需要在初始化的时候也乘以10的1次幂。
以0.05为例,在第一次迭代时它应该和0.5作比较,但这不意味着就是5和5做比较,因为0.05相比0.5有效位少了1位,因此除数也需要在初始化的时候乘以10的1次幂。
在这个基础上,进位的过程可以优化一下。注意,这里优化的目的不是为了时间意义的优化,而是让除数在迭代的过程中不要太大。
如果除数自身可以被2整除,那么就将除数减半,被除数不变。
至于Precision 参数没啥好说的,纯粹是控制迭代次数的活。
代码
说完流程就可以贴代码了
def BinaryFromDecimal(Number,Precision,PrecedingZeroes=0):
BinaryString=""
Remainder=Number
# Initialize the divisor
Extension=len(str(Number))-1+PrecedingZeroes
Divisor=5*(10**Extension)
# Start iteration.
Digit=0
while Digit<Precision:
# Compare dividend and divisor.
if Remainder>=Divisor:
BinaryString+="1"
Remainder-=Divisor
else:
BinaryString+="0"
# If there is no remainder, conversion is over.
if Remainder==0:
break
# Advance divisor and remainder.
if Divisor%2:
# If divisor is not divisible by two,
# advance divisor and remainder by 5 and 10 times.
Remainder*=10
Divisor*=5
else:
# If divisor is divisible by two,
# advancement would be dividing divisor by 2.
Divisor//=2
# Increment the digit total.
Digit+=1
return BinaryString
总结
其实早就有实现好的库了,比方说fxpmath,binary-fractions啥的。
所以才说本文搞出来的是无聊玩意。
|