如何在除10进制以外的进制中计算浮点数?

3
鉴于维基百科上关于基数点的文章,如何计算10.1的二进制等价物或17.17的十六进制等价物?对于前者,十分之一的二进制等价物是什么?对于后者,17/100的十六进制表示是什么?
我更希望得到一个算法,而不仅仅是这两个例子的解决方案。
5个回答

6
要将十进制数10.1转换为二进制,需要将整数部分和小数部分分开并分别进行转换。
将整数部分转换为二进制,使用重复的整数除法运算,并倒序写下余数:
10/2 = 5 余数0
5/2 = 2 余数1
2/2 = 1 余数0
1/2 = 0 余数1
答案:1010
将小数部分转换为二进制,使用重复的乘以2的操作,在每一步中减去整数部分。按生成顺序排列的整数部分表示你的二进制数:
0.1 * 2 = 0.2
0.2 * 2 = 0.4
0.4 * 2 = 0.8
0.8 * 2 = 1.6
0.6 * 2 = 1.2
0.2 * 2 = 0.4
0.4 * 2 = 0.8
...(循环无限重复)
因此,十进制数0.1是二进制数0.000110011001100...

(有关更详细的说明,请参见我的文章http://www.exploringbinary.com/base-conversion-in-php-using-bcmath/中的dec2bin_i()和dec2bin_f()例程。)

对于十六进制,使用相同的过程,但将除数/乘数改为16。余数和大于9的整数部分必须直接转换为十六进制数字:10变成A,11变成B,...,15变成F。


请记得在算法中加入某种停止机制,因为有些十进制数无法在二进制中精确表示。这意味着除非你有一个停止点,否则算法可能会一直运行 =) - Svish
是的,我在说“...(循环无限重复)”时就是在暗示这个意思,而且我链接的那篇文章中的代码也讨论了这个问题。 - Rick Regan

3

这个算法相当简单,但在实践中,您可以通过查找表和日志进行许多微调以加快速度。 但对于基本算法,您可以尝试以下内容:

shift=0;

while v>=base,  v=v/base, shift=shift+1;  

Next digit: 
if v<1.0 && shift==0, output the decimal point
else 
   D=floor(v)
   output D
   v=v-D
v=v*base
shift = shift-1
if (v==0) exit;
goto Next Digit

您还可以在其中放置一个测试,以便在更长的重复小数后停止打印N位数字。


希望我能打勾两个事情。这个和被打勾的那个一样值得打勾。 - bugmagnet

3
一个有限位数可以表示的终止数字n1在b1进制下,可能在不同的进制b2下成为一个无限循环小数。反之,在b1进制下的无限循环小数可能在b2进制下成为一个有限位数可以表示的终止数字。
当将数字0.110转换为二进制时,它是一个无限循环小数,而0.1710转换为十六进制时也是如此。但是,在3进制中,终止数字0.13在转换为十进制后,变成了无限循环小数0.(3)10(表示数字3重复)。同样地,将0.110转换为二进制和0.1710转换为十六进制时,得到的结果是无限循环小数0.0(0011)2和0.2(B851E)16
因此,当从一种进制转换为另一种进制时,你可能需要近似表示这个数字,而不是完全准确地表示。

1

十分之一的二进制等价于二分之一,即不是1/10^1,而是1/2^1。

每个数字代表2的幂。小数点后面的数字是相同的,只是它们代表2的负幂:

 8 4 2 1 . 1/2 1/4 1/8 1/16 

因此,对于10.1,显然需要一个“8”和一个“2”来组成10部分。 1/2(0.5)太多了,1/4(0.25)也太多了,1/8(0.125)也太多了。 我们需要1/16(0.0625),这将使我们剩下0.0375。 1/32是0.03125,所以我们也可以采用它。 到目前为止,我们有:

 8 4 2 1 . 1/2 1/4 1/8 1/16 1/32
 1 0 1 0    0   0   0   1     1

误差为0.00625。1/64(0.015625)和1/128(0.0078125)都太大了,1/256(0.00390625)就可以了:

 8 4 2 1 . 1/2 1/4 1/8 1/16 1/32 1/64 1/128 1/256
 1 0 1 0    0   0   0   1     1    0   0     1

误差为0.00234375。

十进制小数.1在二进制中无法准确表达(就像1/3在十进制下无法准确表达一样)。根据小数点的位置,你最终不得不停止、可能四舍五入,并接受误差。


0

在我使用GMP库进行调整之前,这里是我尝试使Rick Regan的PHP代码适用于从2到36的任何进制的地方。

Function dec2base_f(ByVal ddecimal As Double, ByVal nBase As Long, ByVal dscale As Long) As String
    Const BASES = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" 'up to base 36
    Dim digitCount As Long
    Dim wholeNumber As Double
    Dim digit As String * 1
    digitCount = 0
    dscale = max(dscale, Len(CStr(ddecimal)) - Len("0."))
    Dim baseary_f As String
    baseary_f = "0."
    Do While ddecimal > 0 And digitCount < dscale
        ddecimal = ddecimal * nBase
        digit = Mid$(BASES, Fix(ddecimal) + 1)
        baseary_f = baseary_f & digit '"1"
        ddecimal = ddecimal - Fix(ddecimal)
        digitCount = digitCount + 1
    Loop
    dec2base_f = baseary_f
End Function

Function base2dec_f(ByVal baseary_f As String, nBase As Double) As Double
    Const BASES As String = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    Dim decimal_f As Double
    Dim i As Long
    Dim c As Long
    For i = Len(baseary_f) To Len("0.") + 1 Step -1
        c = InStr(BASES, Mid$(baseary_f, i, 1)) - 1
        decimal_f = decimal_f + c
        decimal_f = decimal_f / nBase
    Next
    base2dec_f = decimal_f
End Function

Debug.Print base2dec_f(dec2base_f(0.09, 2, 200), 2) --> 0.09
Debug.Print base2dec_f(dec2base_f(0.09, 8, 200), 8) --> 0.09
Debug.Print base2dec_f(dec2base_f(0.09, 16, 200), 16) --> 0.09

我的PHP代码基于BCMath,这是一个基于任意精度、十进制字符串的库。而你的代码则基于双精度浮点二进制。我不确定通过这种方式来改编我的代码是否能够达到你想要的效果。 - Rick Regan
一旦我停止使用Double并开始使用GMP,我将得到我想要的东西。上面的代码更多是一个概念证明而不是生产代码。我已经用GMP做了很多事情,现在,由于你的帮助,我可以做像=BASEPOWER(16、123,“-A”)这样的事情,即将0x123升至负A。 - bugmagnet
我假设您正在使用GMP浮点数(类型mpf_t)?来自GMP手册(http://gmplib.org/gmp-man-4.3.0.pdf),第48页:“尾数以二进制形式存储...这样做的一个后果是,十进制小数如0.1无法精确表示。”因此,请确保使用足够的精度,以便根据所使用的基数(例如,一个基数32位数字等于五个基数2位数字)将转换准确到所需的位数。 - Rick Regan
哎呀,额,嗯,还有其他一些声音,当有人指出我忽略的东西时,我会发出这些声音。谢谢你的提醒。 - bugmagnet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接