Jeśli myślisz o użyciu liczb zmiennoprzecinkowych do pomocy w arytmetyce liczb całkowitych, musisz być ostrożny.
Zwykle staram się unikać obliczeń FP, gdy tylko jest to możliwe.
Operacje zmiennoprzecinkowe nie są dokładne. Nigdy nie możesz wiedzieć na pewno, co zostanie (int)(Math.log(65536)/Math.log(2))
ocenione. Na przykład Math.ceil(Math.log(1<<29) / Math.log(2))
na moim komputerze jest 30, podczas gdy matematycznie powinno być dokładnie 29. Nie znalazłem wartości dla x, gdzie (int)(Math.log(x)/Math.log(2))
zawodzi (tylko dlatego, że są tylko 32 „niebezpieczne” wartości), ale nie oznacza to, że zadziała tak samo na każdym komputerze.
Typowa sztuczka polega na używaniu „epsilon” podczas zaokrąglania. Nie (int)(Math.log(x)/Math.log(2)+1e-10)
powinno nigdy zawieść. Wybór tego „epsilonu” nie jest łatwym zadaniem.
Więcej demonstracji, używając bardziej ogólnego zadania - próba wdrożenia int log(int x, int base)
:
Kod testowy:
static int pow(int base, int power) {
int result = 1;
for (int i = 0; i < power; i++)
result *= base;
return result;
}
private static void test(int base, int pow) {
int x = pow(base, pow);
if (pow != log(x, base))
System.out.println(String.format("error at %d^%d", base, pow));
if(pow!=0 && (pow-1) != log(x-1, base))
System.out.println(String.format("error at %d^%d-1", base, pow));
}
public static void main(String[] args) {
for (int base = 2; base < 500; base++) {
int maxPow = (int) (Math.log(Integer.MAX_VALUE) / Math.log(base));
for (int pow = 0; pow <= maxPow; pow++) {
test(base, pow);
}
}
}
Jeśli użyjemy najprostszej implementacji logarytmu,
static int log(int x, int base)
{
return (int) (Math.log(x) / Math.log(base));
}
to drukuje:
error at 3^5
error at 3^10
error at 3^13
error at 3^15
error at 3^17
error at 9^5
error at 10^3
error at 10^6
error at 10^9
error at 11^7
error at 12^7
...
Aby całkowicie pozbyć się błędów musiałem dodać epsilon, który jest między 1e-11 a 1e-14. Czy mogłeś to powiedzieć przed testowaniem? Zdecydowanie nie mogłem.