Quelle est la meilleure méthode pour trouver le nombre de chiffres d'un entier positif?
J'ai trouvé ces 3 méthodes de base:
conversion en chaîne
String s = new Integer(t).toString();
int len = s.length();
for loop
for(long long int temp = number; temp >= 1;)
{
temp/=10;
decimalPlaces++;
}
calcul logaritmique
digits = floor( log10( number ) ) + 1;
où vous pouvez calculer log10 (x) = ln (x)/ln (10) dans la plupart des langues.
J'ai d'abord pensé que la méthode des cordes était la plus sale, mais plus j'y pense, plus je pense que c'est le moyen le plus rapide. Ou est-ce?
Il y a toujours cette méthode:
n = 1;
if ( i >= 100000000 ) { n += 8; i /= 100000000; }
if ( i >= 10000 ) { n += 4; i /= 10000; }
if ( i >= 100 ) { n += 2; i /= 100; }
if ( i >= 10 ) { n += 1; }
Je ne sais pas, et la réponse pourrait bien être différente selon la façon dont votre langue individuelle est implémentée.
Alors, testez-le! Mettez en œuvre les trois solutions. Exécutez-les sur 1 à 1 000 000 (ou sur un autre ensemble énorme de chiffres représentatifs des nombres contre lesquels la solution sera exécutée) et déterminez le temps nécessaire pour chacun d'eux.
Mettez vos solutions les unes contre les autres et laissez-les se battre. Comme des gladiateurs intellectuels. Trois algorithmes entrent! Un algorithme part!
Eh bien, la bonne réponse serait de le mesurer - mais vous devriez être en mesure de deviner le nombre d'étapes du processeur impliquées dans la conversion des chaînes et leur passage à la recherche d'un marqueur de fin
Réfléchissez ensuite au nombre d'opérations FPU/s que votre processeur peut effectuer et à la facilité de calcul d'un seul journal.
edit: perdre un peu plus de temps un lundi matin :-)
String s = new Integer(t).toString();
int len = s.length();
L'un des problèmes des langages de haut niveau est de deviner la quantité de travail que fait le système dans les coulisses d'une déclaration apparemment simple. Obligatoire lien Joel
Cette instruction implique l'allocation de mémoire pour une chaîne, et éventuellement quelques copies temporaires d'une chaîne. Il doit analyser l'entier et en copier les chiffres dans une chaîne, éventuellement en réallouant et en déplaçant la mémoire existante si le nombre est grand. Il devra peut-être vérifier un tas de paramètres régionaux pour décider si votre pays utilise "," ou ".", Il devra peut-être faire un tas de conversions Unicode.
Ensuite, pour trouver la longueur, il faut parcourir toute la chaîne, en tenant compte à nouveau de l'unicode et de tout paramètre local spécifique tel que - êtes-vous dans une langue droite-> gauche ?.
Alternativement:
digits = floor( log10( number ) ) + 1;
Ce n'est pas parce que ce serait plus difficile à faire sur papier que c'est difficile pour un ordinateur! En fait, une bonne règle dans le calcul haute performance semble avoir été - si quelque chose est difficile pour un humain (dynamique des fluides, rendu 3D), c'est facile pour un ordinateur, et si c'est facile pour un humain (reconnaissance faciale, détection d'une voix dans un chambre bruyante) c'est dur pour un ordinateur!
Vous pouvez généralement supposer que les fonctions mathématiques intégrées log/sin/cos, etc. - ont été une partie importante de la conception informatique pendant 50 ans. Donc, même s'ils ne correspondent pas directement à une fonction matérielle du FPU, vous pouvez parier que l'implémentation alternative est assez efficace.
Cet algorithme pourrait également être bon, en supposant que:
Nous ne connaissons pas les limites des nombres
var num = 123456789L;
var len = 0;
var tmp = 1L;
while(tmp < num)
{
len++;
tmp = (tmp << 3) + (tmp << 1);
}
Cet algorithme devrait avoir une vitesse comparable à celle de for-loop (2), mais un peu plus rapide en raison de (2 décalages de bits, ajouter et soustraire, au lieu de la division).
Quant à l'algorithme Log10, il ne vous donnera qu'une réponse approximative (qui est proche de la réalité, mais quand même), car la formule analytique pour calculer la fonction Log a une boucle infinie et ne peut pas être calculée avec précision Wiki .
Conditions de test
Résultats
chiffres: [1,10],
non. de pistes: 1 000 000
échantillon aléatoire: 8777509,40442298,477894,329950,513,91751410,313,3159,131309,2
résultat: 7,8,6,6,3,8,3,4,6,1
CONVERSION EN CHAÎNE: 724 ms
CALCUL LOGARITMIQUE: 349ms
DIV 10 ITERATION: 229ms
CONDITIONNEMENT MANUEL: 136 ms
Remarque: L'auteur s'abstient de tirer des conclusions pour les nombres à plus de 10 chiffres.
Script
package {
import flash.display.MovieClip;
import flash.utils.getTimer;
/**
* @author Daniel
*/
public class Digits extends MovieClip {
private const NUMBERS : uint = 1000000;
private const DIGITS : uint = 10;
private var numbers : Array;
private var digits : Array;
public function Digits() {
// ************* NUMBERS *************
numbers = [];
for (var i : int = 0; i < NUMBERS; i++) {
var number : Number = Math.floor(Math.pow(10, Math.random()*DIGITS));
numbers.Push(number);
}
trace('Max digits: ' + DIGITS + ', count of numbers: ' + NUMBERS);
trace('sample: ' + numbers.slice(0, 10));
// ************* CONVERSION TO STRING *************
digits = [];
var time : Number = getTimer();
for (var i : int = 0; i < numbers.length; i++) {
digits.Push(String(numbers[i]).length);
}
trace('\nCONVERSION TO STRING - time: ' + (getTimer() - time));
trace('sample: ' + digits.slice(0, 10));
// ************* LOGARITMIC CALCULATION *************
digits = [];
time = getTimer();
for (var i : int = 0; i < numbers.length; i++) {
digits.Push(Math.floor( Math.log( numbers[i] ) / Math.log(10) ) + 1);
}
trace('\nLOGARITMIC CALCULATION - time: ' + (getTimer() - time));
trace('sample: ' + digits.slice(0, 10));
// ************* DIV 10 ITERATION *************
digits = [];
time = getTimer();
var digit : uint = 0;
for (var i : int = 0; i < numbers.length; i++) {
digit = 0;
for(var temp : Number = numbers[i]; temp >= 1;)
{
temp/=10;
digit++;
}
digits.Push(digit);
}
trace('\nDIV 10 ITERATION - time: ' + (getTimer() - time));
trace('sample: ' + digits.slice(0, 10));
// ************* MANUAL CONDITIONING *************
digits = [];
time = getTimer();
var digit : uint;
for (var i : int = 0; i < numbers.length; i++) {
var number : Number = numbers[i];
if (number < 10) digit = 1;
else if (number < 100) digit = 2;
else if (number < 1000) digit = 3;
else if (number < 10000) digit = 4;
else if (number < 100000) digit = 5;
else if (number < 1000000) digit = 6;
else if (number < 10000000) digit = 7;
else if (number < 100000000) digit = 8;
else if (number < 1000000000) digit = 9;
else if (number < 10000000000) digit = 10;
digits.Push(digit);
}
trace('\nMANUAL CONDITIONING: ' + (getTimer() - time));
trace('sample: ' + digits.slice(0, 10));
}
}
}
conversion en chaîne: cela devra parcourir chaque chiffre, trouver le caractère qui correspond au chiffre actuel, ajouter un caractère à une collection de caractères. Obtenez ensuite la longueur de l'objet String résultant. S'exécutera en O(n) pour n = # chiffres.
for-loop: effectuera 2 opérations mathématiques: diviser le nombre par 10 et incrémenter un compteur. S'exécutera en O(n) pour n = # chiffres.
logarithmique: appellera log10 et floor, et ajoutera 1. Ressemble à O(1) mais je ne sais pas vraiment à quelle vitesse les fonctions log10 ou floor sont. Ma connaissance de ce genre de choses a atrophié par manque d'utilisation donc il pourrait y avoir caché complexité dans ces fonctions.
Donc je suppose que cela se résume à: recherche les mappages de chiffres plus rapidement que plusieurs opérations mathématiques ou tout ce qui se passe dans log10
? La réponse variera probablement. Il pourrait y avoir des plates-formes où le mappage de caractères est plus rapide et d'autres où les calculs sont plus rapides. Il faut également garder à l'esprit que la première méthode créera un nouvel objet String qui n'existe que dans le but d'obtenir la longueur. Cela utilisera probablement plus de mémoire que les deux autres méthodes, mais cela peut ou non avoir de l'importance.
Vous pouvez évidemment éliminer la méthode 1 de la concurrence, car l'algorithme atoi/toString qu'il utilise serait similaire à la méthode 2.
La vitesse de la méthode 3 dépend de la compilation du code pour un système dont le jeu d'instructions comprend la base de journal 10.
Utilisez la solution la plus simple dans le langage de programmation que vous utilisez. Je ne peux pas penser à un cas où le comptage des chiffres dans un entier serait le goulot d'étranglement dans tout programme (utile).
char buffer[32];
int length = sprintf(buffer, "%ld", (long)123456789);
len = (length . show) 123456789
length = String(123456789).length;
$length = strlen(123456789);
length = Len(str(123456789)) - 1
En ce qui concerne les trois méthodes que vous proposez pour "déterminer le nombre de chiffres nécessaires pour représenter un nombre donné dans une base donnée", je n'aime aucune d'entre elles, en fait; Je préfère plutôt la méthode que je donne ci-dessous.
Concernant votre méthode n ° 1 (chaînes): tout ce qui implique une conversion entre les chaînes et les nombres est généralement très lent.
Concernant votre méthode n ° 2 (temp/= 10): Ceci est fatalement défectueux car il suppose que x/10 signifie toujours "x divisé par 10". Mais dans de nombreux langages de programmation (par exemple: C, C++), si "x" est un type entier, "x/10" signifie "division entière", ce qui n'est pas la même chose que la division à virgule flottante, et il introduit les erreurs d'arrondi à chaque itération, et elles s'accumulent dans une formule récursive telle que celle utilisée par votre solution n ° 2.
Concernant votre méthode n ° 3 (journaux): elle est boguée pour les grands nombres (au moins en C, et probablement aussi dans d'autres langues), car les types de données à virgule flottante ont tendance à ne pas être aussi précis que les entiers 64 bits.
Par conséquent, je n'aime pas ces 3 méthodes: # 1 fonctionne mais est lent, # 2 est cassé et # 3 est bogué pour les grands nombres. Au lieu de cela, je préfère cela, qui fonctionne pour les nombres de 0 à environ 18,44 quintillions:
unsigned NumberOfDigits (uint64_t Number, unsigned Base)
{
unsigned Digits = 1;
uint64_t Power = 1;
while ( Number / Power >= Base )
{
++Digits;
Power *= Base;
}
return Digits;
}
import math
def numdigits(n):
return ( int(math.floor(math.log10(n))) + 1 )
Pour les très grands entiers, la méthode log est beaucoup plus rapide. Par exemple, avec un numéro à 2491327 chiffres (le 11920928e numéro de Fibonacci, si vous voulez), Python prend plusieurs minutes pour exécuter l'algorithme de division par 10, et des millisecondes pour exécuter la fonction 1+floor(log(n,10))
.
Rester simple:
long long int a = 223452355415634664;
int x;
for (x = 1; a >= 10; x++)
{
a = a / 10;
}
printf("%d", x);
Vous pouvez utiliser une solution récursive au lieu d'une boucle, mais en quelque sorte similaire:
@tailrec
def digits (i: Long, carry: Int=1) : Int = if (i < 10) carry else digits (i/10, carry+1)
digits (8345012978643L)
Avec les longs, l'image peut changer - mesurez des nombres petits et longs indépendamment par rapport à différents algorithmes, et choisissez celui qui convient, en fonction de votre entrée typique. :)
Bien sûr, rien ne vaut un interrupteur:
switch (x) {
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9: return 1;
case 10: case 11: // ...
case 99: return 2;
case 100: // you get the point :)
default: return 10; // switch only over int
}
sauf un plain-o-array:
int [] size = {1,1,1,1,1,1,1,1,1,2,2,2,2,2,... };
int x = 234561798;
return size [x];
Certaines personnes vous diront d'optimiser la taille du code, mais yaknow, optimisation prématurée ...
Voici la mesure en Swift 4.
Code d'algorithmes:
extension Int {
var numberOfDigits0: Int {
var currentNumber = self
var n = 1
if (currentNumber >= 100000000) {
n += 8
currentNumber /= 100000000
}
if (currentNumber >= 10000) {
n += 4
currentNumber /= 10000
}
if (currentNumber >= 100) {
n += 2
currentNumber /= 100
}
if (currentNumber >= 10) {
n += 1
}
return n
}
var numberOfDigits1: Int {
return String(self).count
}
var numberOfDigits2: Int {
var n = 1
var currentNumber = self
while currentNumber > 9 {
n += 1
currentNumber /= 10
}
return n
}
}
Code de mesure:
var timeInterval0 = Date()
for i in 0...10000 {
i.numberOfDigits0
}
print("timeInterval0: \(Date().timeIntervalSince(timeInterval0))")
var timeInterval1 = Date()
for i in 0...10000 {
i.numberOfDigits1
}
print("timeInterval1: \(Date().timeIntervalSince(timeInterval1))")
var timeInterval2 = Date()
for i in 0...10000 {
i.numberOfDigits2
}
print("timeInterval2: \(Date().timeIntervalSince(timeInterval2))")
Sortie
timeInterval0: 1.92149806022644
timeInterval1: 0.557608008384705
timeInterval2: 2.83262193202972
Sur cette base de mesure, la conversion de chaînes est la meilleure option pour la langue Swift.
J'étais curieux après avoir vu les résultats de @ daniel.sedlacek, j'ai donc fait des tests en utilisant Swift pour les nombres à plus de 10 chiffres. J'ai exécuté le script suivant dans la cour de récréation.
let base = [Double(100090000000), Double(100050000), Double(100050000), Double(100000200)]
var rar = [Double]()
for i in 1...10 {
for d in base {
let v = d*Double(arc4random_uniform(UInt32(1000000000)))
rar.append(v*Double(arc4random_uniform(UInt32(1000000000))))
rar.append(Double(1)*pow(1,Double(i)))
}
}
print(rar)
var timeInterval = NSDate().timeIntervalSince1970
for d in rar {
floor(log10(d))
}
var newTimeInterval = NSDate().timeIntervalSince1970
print(newTimeInterval-timeInterval)
timeInterval = NSDate().timeIntervalSince1970
for d in rar {
var c = d
while c > 10 {
c = c/10
}
}
newTimeInterval = NSDate().timeIntervalSince1970
print(newTimeInterval-timeInterval)
Résultats de 80 éléments
,10506987571716 pour le sol (log10 (x))
.867973804473877 pour les itérations div 10
log(x,n)-mod(log(x,n),1)+1
Où x est la base et n le nombre.