5

今天,我遇到了一个很奇怪的问题。我需要计算一个数字的字符串长度,所以我想出了这个解决方案

// say the number is 1000
(int)(log(1000)/log(10)) + 1

这是基于数学公式

log10 x = logn x/logn10在此解释)

但我发现,在 C 语言中,

(int)(log(1000)/log(10)) + 1

不等于_

(int) log10(1000) + 1

但它应该是。

我什至用这段代码在 Java 中尝试了同样的事情

(int) (Math.log(1000) / Math.log(10)) + 1
(int) Math.log10(1000) + 1

但它的行为同样错误。

故事还在继续。执行此代码后

for (int i = 10; i < 10000000; i *= 10) {
   System.out.println(((int) (Math.log10(i)) + 1) + 
                " " + ((int) (Math.log(i) / Math.log(10)) + 1));
}

我明白了

2 2
3 3
4 3  // here second method produces wrong result for 1000
5 5
6 6
7 6  // here again

所以这个错误似乎发生在每 1000 的倍数上。

我把这个给我的C老师看,他说可能是对数除法时的类型转换错误,但不知道为什么。

所以我的问题是

  • 根据数学,为什么不(int) (Math.log(1000) / Math.log(10)) + 1等于(int) Math.log10(1000) + 1 ,而应该是。
  • 为什么只有 1000 的倍数是错误的?

编辑:这不是舍入误差,因为

Math.floor(Math.log10(i)) + 1
Math.floor(Math.log(i) / Math.log(10)) + 1

产生相同的错误输出

2 2
3 3
4 3
5 5
6 6
7 6

edit2:我必须四舍五入,因为我想知道位数

log10(999) + 1 = 3.9995654882259823
log10(1000) + 1 =  4.0

如果我只是四舍五入,我会得到相同的结果 (4),这对于 999 是错误的,因为它有 3 位数字。

4

8 回答 8

23

您提供了代码片段

for (int i = 10; i < 10000000; i *= 10) {
   System.out.println(((int) (Math.log10(i)) + 1) + 
                " " + ((int) (Math.log(i) / Math.log(10)) + 1));
}

来说明你的问题。只需删除演员int表并再次运行循环。您将收到

2.0 2.0
3.0 3.0
4.0 3.9999999999999996
5.0 5.0
6.0 6.0
7.0 6.999999999999999

立即回答您的问题。正如 tliff 已经争论的那样,演员表截断了小数,而不是正确地四舍五入。

编辑:您更新了要使用的问题floor(),但是像铸造一样floor()会四舍五入因此会去掉小数点!

于 2009-09-30T11:05:37.353 回答
8

日志操作是一个超越函数。计算机可以做的最好的评估结果是使用代数函数来逼近所需的操作。结果的准确性取决于计算机使用的算法(这可能是 FPU 中的微码)。在英特尔 FPU 上,有一些设置会影响各种超越函数(三角函数也是超越函数)的精度,并且 FPU 规范将详细说明所使用的各种算法的精度水平。

因此,除了上面提到的舍入误差之外,还有一个准确性问题,因为计算的 log(x) 可能不等于实际的 log(x)。

于 2009-09-30T11:15:49.753 回答
5

由于精度和舍入问题。 Math.log(1000) / Math.log(10)不完全等于 3。

如果您需要精确的精度,请不要使用浮点算术 - 并且通常放弃对数。浮点数本质上是模糊的。要获得精确的结果,请使用整数算术。

我真的建议你一般不要走这条路,但听起来你正在取整数的对数来确定某个数量级。如果是这种情况,那么 (int)(Math.log(x+0.5) / Math.log(10))将更加稳定 - 但要意识到double' 仅具有 53 位精度,因此第 15 个双精度数的 10 左右不再能够准确地表示整数,并且这个技巧将不起作用。

于 2009-09-30T11:11:24.380 回答
4

为分子添加一个非常小的值以绕过 Skizz 指出的准确性问题。

// say the number is 1000
(int)((log(1000)+1E-14)/log(10)) + 1

1E-14 应该足以将精度推回正轨。

将小值从 1E-15 更改为某些输入会给出不正确的结果

我用 1E-14 测试了unsigned long longs 的随机样本,我的所有数字都通过了。

于 2009-09-30T11:30:36.173 回答
2

更新:这是由于精度和舍入误差

于 2009-09-30T10:56:57.630 回答
0

如果你想让你的结果作为一个整数,你可能应该四舍五入而不是仅仅在点之后切断部分。

您可能会得到类似 6.999999 的值并将其四舍五入为 6。

于 2009-09-30T10:59:05.083 回答
0

使用 (int) 强制转换,您将切断必要的小数部分。尝试在不铸造的情况下将它们打印为双打(为什么还要铸造?),你会没事的。

于 2009-09-30T11:05:18.477 回答
0

打印出中间结果,即log(1000)、log(10)、log(1000)/log(10)和log10(1000)。这应该比猜测提供更好的提示。

于 2009-09-30T11:08:22.740 回答