为什么log(1000)/ log(10)与log10(1000)不同?
今天,我遇到了一个很奇怪的问题。 我需要计算一个数字的字符串长度,所以我提出了这个解决方案
// say the number is 1000 (int)(log(1000)/log(10)) + 1
这是基于数学公式
log
10 x = log
n x/log
n 10
( 此处说明)
但我发现,在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位数。
您提供了代码段
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()
将向下舍入,因此删除小数!
日志操作是一个超越函数 。 计算机评估结果的最佳方法是使用近似于所需操作的代数函数 。 结果的准确性取决于计算机使用的算法(这可能是FPU中的微码)。 在英特尔FPU上,有一些设置影响各种超越函数的精度(三角函数也是超越的),FPU规范将详细说明所使用的各种算法的准确度。
因此,除了上面提到的舍入误差之外,还存在精度问题,因为计算的log(x)可能不等于实际log(x)。
这是由于精度和舍入问题。 Math.log(1000) / Math.log(10)
并不精确等于3。
如果您需要精确的精度,请不要使用浮点运算 – 并且通常会放弃对数。 浮点数本质上是模糊的。 要获得精确的结果,请使用整数运算。
我真的建议你不要沿着这条路走下去,但听起来你要把整数的对数确定为一个数量级。 如果是这种情况,那么(int)(Math.log(x+0.5) / Math.log(10))
会更稳定 – 但是要意识到double
精度只有53位精度,所以大约10位是第15次双精度不能再完全代表整数,这个技巧就行不通了。
为分子添加一个非常小的值,以绕过Skizz指出的准确性问题。
// say the number is 1000 (int)((log(1000)+1E-14)/log(10)) + 1
1E-14应该足以将准确性推回到轨道中。
改变了1E-15的小值,这会给某些输入带来不正确的结果
我用1E-14测试了unsigned long long
的随机样本,并且我的所有数字都通过了。
更新:这是由于精度和舍入误差
如果你想把你的结果作为一个整数,你可能应该四舍五入,而不仅仅是在一点一点之后切断它。
你可能得到像6.999999这样的东西并将其四舍五入到6。
使用(int)转换,您将切断必要的小数部分。 尝试将它们打印为双打而不进行铸造(为什么还要进行铸造?),你会没事的。
打印出中间结果,即log(1000),log(10),log(1000)/ log(10)和log10(1000)。 这应该提供比猜测更好的提示。