唐凌 发表于 2022-9-28 12:28:17

【Python】十进制小数转二进制小数

本帖最后由 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`参数没啥好说的,纯粹是控制迭代次数的活。

## 代码
说完流程就可以贴代码了
```Python
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
```

# 总结
其实早就有实现好的库了,比方说(https://github.com/francof2a/fxpmath),(https://pypi.org/project/binary-fractions/)啥的。
所以才说本文搞出来的是无聊玩意。

Golden Blonde 发表于 2022-10-6 21:51:03

原来PYTHON的代码是这样的。。。我实在受不了没有括号也有没有end xxx的代码。
页: [1]
查看完整版本: 【Python】十进制小数转二进制小数