简体   繁体   English

转换浮点数的基数而不会丢失精度

[英]Converting base of floating point number without losing precision

Terminology 术语

In this question I am calling "floating point number" "decimal number" to prevent ambiguation with the float / double Java primitive data types. 在这个问题中,我称之为“浮点数”“十进制数”,以防止使用float / double Java原始数据类型进行ambiguation。 The term "decimal" has no relationship with "base 10". 术语“十进制”与“基数10”无关。

Background 背景

I am expressing a decimal number of any base in this way: 我用这种方式表示任何基数的十进制数:

class Decimal{
    int[] digits;
    int exponent;
    int base;
    int signum;
}

which approximately expresses this double value: 它近似表示这个double值:

public double toDouble(){
    if(signum == 0) return 0d;
    double out = 0d;
    for(int i = digits.length - 1, j = 0; i >= 0; i--, j++){
        out += digits[i] * Math.pow(base, j + exponent);
    }
    return out * signum;
}

I am aware that some conversions are not possible. 我知道有些转换是不可能的。 For example, it is not possible to convert 0.1 (base 3) to base 10, because it is a recurring decimal. 例如,不可能将0.1 (base 3)转换0.1 (base 3)基数10,因为它是重复的小数。 Similarly, converting 0.1 (base 9) to base 3 is not possible, but covnerting 0.3 (base 3) is possible. 类似地,不可能将0.1 (base 9)转化0.1 (base 9)碱3,但是可以将共价0.3 (base 3)转化。 There are probably other cases that I have not considered. 可能还有其他一些我没有考虑过的案例。

The traditional way 传统的方式

The traditional way (by hand) of change of base, for integers, from base 10 to base 2, is to divide the number by the exponents of 2, and from base 2 to base 10 is to multiply the digits by respective exponents of 2. Changing from base x to base y usually involves converting to base 10 as an intermediate. 对于整数,从基数10到基数2的基数变化的传统方式(手动)是将数字除以2的指数,并且从基数2到基数10将数字乘以2的相应指数。从基数x变为基数y通常涉及转换为基数10作为中间体。

First question: Argument validation 第一个问题:参数验证

Therefore, my first question is, if I were to implement the method public Decimal Decimal.changeBase(int newBase) , how can I validate whether newBase can be made without resulting in recurring decimals (which is incompatible with the design of the int[] digits field, since I don't plan to make an int recurringOffset field just for this. 因此,我的第一个问题是,如果我要实现方法public Decimal Decimal.changeBase(int newBase) ,我如何验证是否可以在不产生重复小数的情况下生成newBase (这与int[] digits的设计不兼容int[] digits字段,因为我不打算为此创建一个int recurringOffset字段。

Second question: Implementation 第二个问题:实施

Hence, how to implement this? 那么,如何实现呢? I instinctively feel that this question is much easier to solve if the first question is solved. 我本能地觉得如果第一个问题得到解决,这个问题就容易解决了。

Third question: What about recurring number output: 第三个问题:重复数字输出怎么样:

I don't plan to make an int recurringOffset field just for this. 我不打算为此创建一个int recurringOffset字段。

For the sake of future readers, this question should also be asked. 为了将来的读者,也应该问这个问题。

For example, according to Wolfram|Alpha : 例如,根据Wolfram | Alpha

0.1 (base 4) = 0.[2...] (base 9)

How can this be calculated (by hand, if by programming sounds too complicated)? 如何计算(如果通过编程听起来太复杂),可以如何计算?

I think that a data structure like this can represent this decimal number: 我认为像这样的数据结构可以表示这个十进制数:

class Decimal{
    int[] constDigits;
    int exponent;
    int base;
    int signum;
    @Nullable @NonEmpty int[] appendRecurring;
}

For example, 61/55 can be expressed like this: 例如, 61/55可以表示如下:

{
    constDigits: [1, 1], // 11
    exponent: -1, // 11e-1
    base: 10,
    signum: 1, // positive
    appendRecurring: [0, 9]
}


Not a homework question 不是作业问题

I am not looking for any libraries. 我不是在寻找任何图书馆。 Please do not answer this question with reference to any libraries. 请不要参考任何图书馆回答这个问题。 (Because I'm writing this class just for fun, OK?) (因为我正在写这堂课只是为了好玩,好吗?)

To your first question: whenever the prime factors of the old base are also among the prime factors of the new base you can always convert without becoming periodic. 对于你的第一个问题:只要旧基数的素因子也是新基数的主要因子,你总是可以转换而不会成为周期性的。 For example every base 2 number can be represented exactly as base 10. This condition is unfortunately sufficient but not necessary, for example there are some base 10 numbers like 0.5 that can be represented exactly as base 2, although 2 does not have the prime factor 5. 例如,每个基数2的数字可以精确地表示为基数10.不幸的是,这个条件是足够的但不是必需的,例如,有一些基数10的数字,如0.5,可以完全表示为基数2,尽管2没有素数因子5。

When you write the number as fraction and reduce it to lowest terms it can be represented exactly without a periodic part in base x if and only if the denominator has only prime factors that also appear in x (ignoring exponents of primes). 当您将数字写为分数并将其减少到最低项时,如果且仅当分母仅具有也出现在x中的素数因子(忽略素数的指数)时,它可以在基数x中没有周期性部分的情况下精确表示。

For example, if your number is 3/25 you can represent this exactly in every base that has a prime factor 5. That is 5, 10, 15, 20, 25, ... 例如,如果您的数字是3/25,那么您可以在具有素数因子5的每个基数中准确地表示这个数字。即5,10,15,20,25 ......

If the number is 4/175, the denominator has prime factors 5 and 7 and therefore can be represented exactly in base 35, 70, 105, 140, 175, ... 如果数字是4/175,则分母具有素数因子5和7,因此可以精确地表示为基数35,70,105,140,​​175,......

For implementation, you can either work in the old base (basically doing divisions) or in the new base (basically doing multiplications). 为了实现,你可以在旧的基础(基本上做分区)或在新的基础(基本上做乘法)中工作。 I would avoid going through a third base during the conversion. 我会避免在转换期间通过第三个基地。

Since you added periodic representations to your question the best way for conversion seems to be to convert the original representation to a fraction (this can always be done, also for periodic representations) and then convert this to the new representation by carrying out the division. 由于您在问题中添加了周期性表示,因此转换的最佳方式似乎是将原始表示转换为分数(这总是可以完成,也适用于周期性表示),然后通过执行除法将其转换为新表示。

To answer the third part of the question, once you have your fraction reduced (and you found out that the "decimal" expansion will be a recurring fraction), you can detect the recurring part by simply doing the long-hand division and remembering the remainders you've encountered. 要回答问题的第三部分,一旦你的分数减少了(并且你发现“十进制”扩展将是一个重复的分数),你可以通过简单地进行长手分割并记住它来检测重复的部分。你遇到的遗留物。

For example to print out 2/11 in base 6, you do this: 例如,要在基数6中打印出2/11 ,您可以这样做:

2/11    = 0 (rem 2/11)
2*6/11  = 1 (rem 1/11)
1*6/11  = 0 (rem 6/11)
6*6/11  = 3 (rem 3/11)
3*6/11  = 1 (rem 7/11)
7*6/11  = 3 (rem 9/11)
9*6/11  = 4 (rem 10/11)
10*6/11 = 5 (rem 5/11)
5*6/11  = 2 (rem 8/11)
8*6/11  = 4 (rem 4/11)
4*6/11  = 2 (rem 2/11) <-- We've found a duplicate remainder

(Had 2/11 been convertible to a base 6 number of finite length, we would've reached 0 remainder instead.) (如果2/11被转换为基数为6的有限长度,我们就会达到0余数。)

So your result will be 0.[1031345242...]. 所以你的结果将是0. [1031345242 ...]。 You can fairly easily design a data structure to hold this, bearing in mind that there could be several digits before the recurrence begins. 你可以相当容易地设计一个数据结构来保持这一点,记住在重现开始之前可能有几个数字。 Your proposed data structure is good for this. 您建议的数据结构对此有利。

Personally I'd probably just work with fractions, floating point is all about trading in some precision and accuracy for compactness. 就个人而言,我可能只是处理分数,浮点就是关于交易的一些精度和准确性的紧凑性。 If you don't want to compromise on precision, floating point is going to cause you a lot of trouble. 如果你不想在精度上妥协,浮点会给你带来很多麻烦。 (Though with careful design you can get pretty far with it.) (虽然经过精心设计,你可以使用它。)

I waited with this after the reward because this is not directly an answer to your questions rather few hints how to approach your task instead. 我在奖励之后等待这个,因为这不是你问题的直接答案,而是提示如何处理你的任务。

  1. Number format 数字格式

    Arbitrary exponential form of number during base conversion is a big problem. 基本转换期间任意指数形式的数字是一个大问题。 Instead I would convert/normalize your number to form: 相反,我会将您的数字转换/标准化为:

     (sign) mantissa.repetition * base^exp 

    Where unsigned int exp is the exponent of least significant digit of mantissa . 其中unsigned int expmantissa的最低有效位的指数。 The mantissa,repetition could be strings for easy manipulation and printing. mantissa,repetition可以是字符串,便于操作和打印。 But that would limit your max base of coarse. 但这会限制你粗略的最大基数。 For example if you reserve e for exponent then you can use { 0,1,2,..9, A,B,C,...,Z } for digits so max base would be then only 36 (if not counting special characters). 例如,如果您为指数保留e ,则可以使用{ 0,1,2,..9, A,B,C,...,Z }作为数字,因此最大基数将仅为36 (如果不计算特殊的话)字符)。 If that is not enough stay with your int digit representation. 如果这还不够,请使用您的int数字表示。

  2. Base conversion (mantissa) 基本转换(尾数)

    I would handle mantissa as integer number for now. 我现在将尾数作为整数处理。 So the conversion is done simply by dividing mantissa / new_base in the old_base arithmetics. 因此,只需在old_base算术中划分mantissa / new_base即可完成转换。 This can be done on strings directly. 这可以直接在字符串上完成。 With this there is no problem as we can always convert any integer number from any base to any other base without any inconsistencies,rounding or remainders. 有了这个没有问题,因为我们总是可以将任何整数从任何基数转换为任何其他基数而没有任何不一致,舍入或余数。 The conversion could look like: 转换可能如下所示:

     // convert a=1024 [dec] -> c [bin] AnsiString a="1024",b="2",c="",r=""; while (a!="0") { a=divide(r,a,b,10); c=r+c; } // output c = "10000000000" 

    Where: 哪里:

    • a is number in old base which you want to convert a是您要转换的旧基数
    • b is new base in old base representation b是旧基表示的新基础
    • c is number in new base c是新基数

    Used divide function looks like this: 使用除法函数如下所示:

     //--------------------------------------------------------------------------- #define dig2chr(x) ((x<10)?char(x+'0'):char(x+'A'-10)) #define chr2dig(x) ((x>'9')?BYTE(x-'A'+10):BYTE(x-'0')) //--------------------------------------------------------------------------- int compare( const AnsiString &a,const AnsiString &b); // compare a,b return { -1,0,+1 } -> { < , == , > } AnsiString divide(AnsiString &r,const AnsiString &a, AnsiString &b,int base); // return a/b computed in base and r = a%b //--------------------------------------------------------------------------- int compare(const AnsiString &a,const AnsiString &b) { if (a.Length()>b.Length()) return +1; if (a.Length()<b.Length()) return -1; for (int i=1;i<=a.Length();i++) { if (a[i]>b[i]) return +1; if (a[i]<b[i]) return -1; } return 0; } //--------------------------------------------------------------------------- AnsiString divide(AnsiString &r,const AnsiString &a,AnsiString &b,int base) { int i,j,na,nb,e,sh,aa,bb,cy; AnsiString d=""; r=""; // trivial cases e=compare(a,b); if (e< 0) { r=a; return "0"; } if (e==0) { r="0"; return "1"; } // shift b for (sh=0;compare(a,b)>=0;sh++) b=b+"0"; if (compare(a,b)<0) { sh--; b=b.SetLength(b.Length()-1); } // divide for (r=a;sh>=0;sh--) { for (j=0;compare(r,b)>=0;j++) { // r-=b na=r.Length(); nb=b.Length(); for (i=0,cy=0;i<nb;i++) { aa=chr2dig(r[na-i]); bb=chr2dig(b[nb-i]); aa-=bb+cy; cy=0; while (aa<0) { aa+=base; cy++; } r[na-i]=dig2chr(aa); } if (cy) { aa=chr2dig(r[na-i]); aa-=cy; r[na-i]=dig2chr(aa); } // leading zeros removal while ((r.Length()>b.Length())&&(r[1]=='0')) r=r.SubString(2,r.Length()-1); } d+=dig2chr(j); if (sh) b=b.SubString(1,b.Length()-1); while ((r.Length()>b.Length())&&(r[1]=='0')) r=r.SubString(2,r.Length()-1); } return d; } //--------------------------------------------------------------------------- 

    It is written in C++ and VCL . 它是用C ++VCL编写的。 AnsiString is VCL string type with self allocating properties and its members are indexed from 1 . AnsiString是具有自分配属性的VCL字符串类型,其成员从1索引。

  3. Base conversion (repetition) 基本转换(重复)

    There are 2 approaches for this I know of. 我知道有两种方法。 The simpler but with possible round errors is setting the repetition to long enough string sequence and handle as fractional number. 更简单但可能有圆错误的是将重复设置为足够长的字符串序列并将其作为小数处理。 For example rep="123" [dec] then conversion to different base would be done by multiplying by new base in old base arithmetics. 例如, rep="123" [dec]然后转换到不同的基数将通过乘以旧的基础算术中的新基数来完成。 So let create long enough sequence: 所以让我们创建足够长的序列:

     0 + 0.123123123123123 * 2 0 + 0.246246246246246 * 2 0 + 0.492492492492492 * 2 0 + 0.984984984984984 * 2 1 + 0.969969969969968 * 2 1 + 0.939939939939936 * 2 1 + 0.879879879879872 * 2 ... ------------------------------ = "0.0000111..." [bin] 

    With this step you need to make repetition analysis and normalize the number again after exponent correction step (in next bullet). 通过此步骤,您需要进行重复分析并在指数校正步骤后再次标准化数字(在下一个项目符号中)。

    Second approach need to have the repetitions stored as division so you need it in form a/b in old_base . 第二种方法需要将重复存储为除法,因此您需要在old_base中的形式a/b中使用old_base You just convert a , b as integers (the same as mantissa) and then do the division to obtain fractional part + repetition part. 你只需将ab转换为整数(与尾数相同),然后进行除法以获得小数部分+重复部分。

    So now you should have converted number in form: 所以现在你应该在表格中转换了数字:

     mantissa.fractional [new_base] * old_base^exp 

    or: 要么:

     mantissa.fractional+a/b [new_base] * old_base^exp 
  4. Base conversion (exponent) 基本转换(指数)

    You need to change old_base^old_exp to new_base^new_exp . 您需要将old_base^old_exp更改为new_base^new_exp The simplest way is to multiply the number by the old_base^old_exp value in new base arithmetics. 最简单的方法是将数字乘以new base算法中的old_base^old_exp值。 So for starters multiply the whole 所以对于初学者来说,整体而言就是倍增

     mantissa.fractional+(a/b) [new_base] 

    by old_base old_exp times in the new arithmetics (later you can change it to power by squaring or better). 通过old_base old_exp中的old_base old_exp次(稍后您可以通过平方或更好的方式将其更改为幂)。 And after that normalize your number. 然后将你的号码标准化。 So find where the repetition string begins and its digit position relative to . 因此,找到重复字符串开始的位置和相对于其的数字位置. is the new_exp value. new_exp值。

[Notes] [笔记]

For this you will need routines to convert old_base and new_base between each other but as the base is not bignum but just simple small unsigned int instead it should not be any problem for you (I hope). 为此你需要例程来在彼此之间转换old_basenew_base ,但是因为base不是bignum而是简单的小unsigned int而不是它应该对你没有任何问题(我希望)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM