关于C#:如何计算大数之间的除法运算的第一个十进制数字?

How do I compute the first decimal digit of a division between large numbers?

我有两个无符号的长整型X和Y,其中X

(X * 10)/ Y将起作用,但是如果X * 10溢出则会产生错误的结果。如果我有理由相信它足够精确,可以计算出正确的结果,则转换为double会有用。

这是C语言。谢谢您的帮助!


我不愿意通过浮点转换来保证准确的结果,除非尾数具有足够的位来精确表示所有整数值。例如,扁桃体y = 9223372036854775807和x =(y div 10)-1 = 922337203685477579.其中" div "是整数除法。 x / y是0.09999999999999999981568563067746 ...,但是使用双精度值会使您> = 0.1。这是因为双精度数的有效位数只有52位(而y需要61位,而x大约需要58位)。

您可能可以使用80位或128位FP精度,在这种情况下,您将获得正确的答案,因为尾数将> = 64位(ULL是64位,对吗?),因此您将无损表示数字。

我将从近似值开始(使用整数或FP算术),然后尝试乘法以查看答案是否应小于或等于1。关键的见解是,只要您知道两个量之间的差小于最大无符号整数的一半,您仍然可以比较两个可能溢出的整数。例如,当TCP序列号溢出。

如果只想使用整数运算,则下面的函数" fdd(x,y)"有效。我包含了main()来显示一些结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <iostream>
using namespace std;

typedef unsigned char ull; // change char to any integral type e.g. long long

const ull maxull=(ull)-1;
const ull halfull = maxull/2;
typedef unsigned long long asint;

// x = X mod (maxull+1), y= Y mod (maxull+1).  we only know x and y
// if we assume |X-Y|<halfull, then we return X<Y:
inline bool less_mod_near(ull x, ull y) {

    return (x<=halfull == y<=halfull) ? x<y : y>x;
}

// assuming x<y, return first decimal digit of 10x/y (return is in [0..9])
inline int fdd(ull x, ull y) {
// assert(x<y);
 if (x<=maxull/10) return (10*x)/y;
  // for speed, and to ensure that y>10 to avoid division by 0 later
 ull r=y/10;
 if (r*10==y) return x/r;
 ull ub=x/(r+1); // ub >= 10x div y (without overflow)
 ull x10=x*10; // allow overflow
 cout<<"ub="<<(asint)ub<<" x10="<<(asint)x10<<" r="<<(asint)r<<"";
 return less_mod_near(x10,ub) ? ub-1 : ub;
  // we already handled the 10 evenly divides y case
}

int pdd(ull x, ull y,ull mustbe)
{
    ull d=fdd(x,y);
    cout << (asint)x << '/' << (asint)y <<" = ." << (asint)d <<"...";
    if (d!=mustbe) cout <<" (should be"<<(asint)mustbe<<")";
    cout<<endl;
//    assert(a==d);
}

int main() {
    pdd(0,1,0);
    pdd(1,2,5);
    pdd(11,101,1);
    pdd(10,101,0);
    pdd(49,69,7);
    pdd(50,69,7);
    pdd(48,69,6);
    pdd(160,200,8);
    pdd(161,200,8);
    pdd(159,200,7);
    pdd(254,255,9);
}

输出:

1
2
3
4
5
6
7
8
9
10
11
0/1 = .0...
1/2 = .5...
11/101 = .1...
10/101 = .0...
ub=7 x10=234 r=6 49/69 = .7...
ub=7 x10=244 r=6 50/69 = .7...
ub=6 x10=224 r=6 48/69 = .6...
160/200 = .8...
161/200 = .8...
159/200 = .7...
ub=9 x10=236 r=25 254/255 = .9...


Conversion to double would work if I had reason to believe it is precise enough to compute the right result.

如果只需要第一位数字,那么肯定会精确到两倍。

编辑:评论中wrang-wrang \\的反例证明我错了。


转换为Double将使64位中的红利和支配者两者都放弃12位精度。在大多数情况下,它会给您正确的答案,但是,除非有舍入错误,否则有时会出现四舍五入的错误。使用80位浮点格式存储的

编辑:
除非我太困了才能看到错误,否则我认为wrang-wrang \\的答案会起作用。
我早上上班,开车到客户现场需要6个小时。哎呀晚上。

编辑:
最后一件事。 x86使用80位内部表示。我认为,如果您想折腾一些asm指令,会有一些操作码可以将int64转换为float80。与纯C实现相比,这将是一个更优雅,当然更快的解决方案,尽管它不是可移植的。


怎么样

1
x / ((y + 9) / 10)

y 9用于对分母中的y / 10进行四舍五入,因此将总结果四舍五入。

对于大的x和y,它几乎总是正确的,
但这并不完美,示例11/14会产生5。

问题是由于划分,您失去了信息。由于乘以十已经溢出,因此除非使用较大的数字数据类型,否则您将无法解决。


X%Y给出余数R

然后您可以根据R和Y计算答案的小数部分。

1
R / Y

要获取第一个数字,请使用整数除法:

1
(long)((long)10*R/Y)

这应该四舍五入数字并删除任何多余的小数。

编辑:

为了匹配您的问题(对于那些想挑剔的人),其

1
Y % X = R

1
R / X


如果您愿意接受范围限制,则可以整数形式完成所有操作
算术的:-

(X * 10)/ Y

在您的示例中:

1
(11 * 10) / 14

=> 110/14

=> 7

限制是您已将X的最大值减少了10倍。