6

Estoy practicando mi javascript en CodeWars y me he topado con esta pregunta:

Escribe una función que devuelva el String con la suma de dos números. Los parametros son dos numeros pero de tipo String.

Notas:

  • Los inputs son grandes.
  • Los inputs son strings que contienen solo numeros.
  • Los numeros son positivos.

Por lo que hice:

function add(a, b) {
  return (Number(a) + Number(b))+"";
}

Para numeros pequeños no hay problema, me pasa los test bien, pero para numeros grandes falla al cambiar la notación.

Tests:

✔ Test Passed: Value == '100'
✔ Test Passed: Value == '8670'
✔ Test Passed: Value == '5'
✘ sumStrings('712569312664357328695151392', '8100824045303269669937') - 
    Expected: '712577413488402631964821329', instead got: '7.125774134884027e+26'
✘ sumStrings('50095301248058391139327916261', '81055900096023504197206408605') - 
    Expected: '131151201344081895336534324866', instead got: '1.3115120134408189e+29' 

Probablemente sea una chorrada pero no se me ocurre. ¿Cómo puedo solventar este problema?

Pablo Lozano
  • 45,934
  • 7
  • 48
  • 87
lois6b
  • 7,419
  • 5
  • 29
  • 50

5 Answers5

10

Realmente no falla, la suma la realiza correctamente, simplemente que CodeWars no querrá como resultado ese formato.

Por ejemplo:

El número 7.125774134884027e+26 es lo mismo que 712577413488402631964821329 pero simplemente con distinta notación.

No he conseguido otra solución menos compleja que la que te propongo a continuación:

function add(num1, num2) {
    num1 = num1.split('');
  num2 = num2.split('');

  num1 = num1.map(function (num) {
    return parseInt(num, 10);
  });

  num2 = num2.map(function (num) {
    return parseInt(num, 10);
  });

    if (num2.length > num1.length) {
    return _add(num2, num1);
  } else {
    return _add(num1, num2)
  }
}

function _add(num1, num2) {
    var num1_idx = num1.length-1;
  var num2_idx = num2.length-1;
  var remainder = 0;

  for (; num1_idx > -1; num1_idx--, num2_idx--) {
    var sum = num1[num1_idx] + remainder;

    if (num2_idx > -1) {
        sum += num2[num2_idx];
    }

        if (sum <= 9 || num1_idx === 0) {
        remainder = 0;
        num1[num1_idx] = sum;
    } else if (sum >= 10) {
        remainder = 1;
      num1[num1_idx] = sum - 10;
    }

    console.log(remainder);
  }

  return num1.join('').replace(/^[0]+/g,"");
}

Utilizándolo de la siguiente manera:

document.write(add("712577413488402631964821329", "712577413488402631964821329"));

Tienes el GitHub con el creador de este método aquí


Otra solución que he encontrado es utilizar librerías externas para ello como BigInteger y poder utilizarlo así:

var n = bigInt("91942213363574161572522430563301811072406154908250")
    .plus("91942213363574161572522430563301811072406154908250");
cnbandicoot
  • 2,614
  • 8
  • 24
  • aaaaamigo. librerias externas aparte (gracias por la recomendacion) pensaba que el problema era tema de tipo de variable por no entrar el numero y por eso pasaba a notacion cientifica y la solucion seria algun tipo de tratamiento de tipos.. lo que tu propones es un algoritmo que sume uno a uno y arraste a la siguiente suma el reminder no ? no se me ocurrió hacerlo de la manera ... *de colegio* (a lo papel y boli ahahah) Gracias! – lois6b Mar 01 '17 at 10:43
  • pasó a la perfección salvo por un fallo `sumStrings('00103', '08567') - Expected: '8670', instead got: '08670'` (pero quitando los ceros al principio lo resuelve.) – lois6b Mar 01 '17 at 10:45
  • Exacto! Javascript a cierto límite, ya empieza a mostrar los números con esa notación científica, así que hay que hacerlo casi una suma *manual* – cnbandicoot Mar 01 '17 at 10:46
  • si quieres añadirlo, en tu `return` del metodo `_add` puse: `return num1.join('').replace(/^[0]+/g,"");`. – lois6b Mar 01 '17 at 10:52
  • @lois6b Editado ;) – cnbandicoot Mar 01 '17 at 10:53
6

Existe una clase BigInt nativa que está a día de hoy en fase experimental (el estándar ECMAScript la está contemplando) y que sólo está presente en los navegadores Chrome y Firefox (en este caso hay que activarla manualmente) y en NodeJS.

El formato de esta clase no usa el científico y permite el manejo de números enteros arbitrariamente grandes sin perder precisión:

function sumStrings(a, b) {
  return BigInt(a) + BigInt(b)
}

let resultado=sumStrings('712569312664357328695151392', '8100824045303269669937');
console.log(resultado.toString());

Actualización: La propuesta se considera aprobada y será parte de la revisón del estándar ES2020

Pablo Lozano
  • 45,934
  • 7
  • 48
  • 87
3

JavaScript usa double-precision float en el trabajo con números y puede representar números de manera segura entre -(2^53 - 1) y 2^53 - 1. Eso significa que debes trabajar con números entre -9007199254740991 y 9007199254740991, para números más grandes los representará como lo que te devuelve CodeWars (notación científica). Si necesitas trabajar con números más grandes te recomiendo utilizar una librería como BigNumbers.js.

ElChiniNet
  • 3,215
  • 9
  • 25
  • gracias por la respuesta. en este caso concreto no puedo usar librerias externas asi que la solucion es un algoritmo como el de la respuesta de cnbandticoot. – lois6b Mar 01 '17 at 10:46
  • 1
    @lois6b, exacto, si no puedes usar librerías externas debes hacer tú tu propio algoritmo. Pero cuidado, lo mismo me ocurrió en [HackerRank](https://www.hackerrank.com/), te pueden enviar un input con millones de números y algoritmos como estos entonces terminarían en un `timeout`. Para trabajar con números grandes lamentablemente lo mejor es irse a por otro lenguaje. – ElChiniNet Mar 01 '17 at 10:51
  • ya .. ya me tocará otro lenguaje, ahora practico javascript hehe – lois6b Mar 01 '17 at 10:53
3

Yo lo resolví imitando el método del colegio:

tomo la cifra menos significativa de ambos números. Las sumo, anoto la unidad y, si es necesario, me reservo una. Esto equivale a iterar sobre el string más largo e ir quitando de ambos string la cifra menos significativa, sumándolas, obteniendo la decena y luego vuelta a iterar sumando también la decena (que al inicio vale 0).

Ver el siguiente ejemplo:

function sumabigInt(num1,num2) {

            var num1 = String(num1),
                num2 = String(num2),
                acumulado = '',
                arr1 = num1.split(''),
                arr2 = num2.split(''),
                decena = 0,
                maxlength = Math.max(arr1.length, arr2.length);

           var sumadigitos=function(a, b, c) {
                var suma = Number(a) + Number(b) + Number(c),
                    nuevonumero = suma % 10,
                    segundacifra = Math.floor(suma / 10);
                acumulado = String(nuevonumero) + acumulado;
                return segundacifra;
            };

 
            for (var i = 0; i < maxlength; i++) {
                var val1 = arr1.length ? arr1.pop() : 0,
                    val2 = arr2.length ? arr2.pop() : 0;
                decena = sumadigitos(val1, val2, decena);
            }
            if (decena > 0) {
                acumulado = String(decena) + acumulado;
            }
            return acumulado;
 }
console.log(sumabigInt(  '712569312664357328695151392', '8100824045303269669937'));     
 
 

El siguiente ejemplo va imprimiendo una tablita con jQuery.

jQuery(document).ready(function() {
            var num1 = '712569312664357328695151392',
                num2 = '8100824045303269669937',
                acumulado = '',
                arr1 = num1.split(''),
                arr2 = num2.split(''),
                iteracion = 1,
                decena = 0,
                maxlength = Math.max(arr1.length, arr2.length);

            function sumadigitos(a, b, c) {
                var suma = Number(a) + Number(b) + Number(c),
                    nuevonumero = suma % 10,
                    segundacifra = Math.floor(suma / 10);
                acumulado = String(nuevonumero) + acumulado;
                return segundacifra;
            }

            function imprime(iteracion) {
                var newrow = jQuery('<tr></tr>');
                newrow.append('<td >Iteracion ' + iteracion + '</td>');
                newrow.append('<td >' + arr1.join('') + '</td>');
                newrow.append('<td >' + arr2.join('') + '</td>');
                newrow.append('<td >' + acumulado + '</td>');
                newrow.appendTo('#container');
            }
            imprime(iteracion);
            for (var i = 0; i < maxlength; i++) {
                var val1 = arr1.length ? arr1.pop() : 0,
                    val2 = arr2.length ? arr2.pop() : 0;
                decena = sumadigitos(val1, val2, decena);
                iteracion++;
                imprime(iteracion);
            }
            if (decena > 0) {
                acumulado = String(decena) + acumulado;
                imprime('final');
            }
        });
td {
            border: 1px solid #ccc;
        }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table id="container" style="width:550px;font-size:9px;">
            <tr>
                <th width="15%">
                    Comentario
                </th>
                <th width="26%">
                    num1
                </th>
                <th width="26%">
                    num2
                </th>
                <th width="29%">
                    acumulado
                </th>
            </tr>
        </table>

        
ffflabs
  • 21,223
  • 25
  • 48
  • gracias por tu respuesta. me gusta mucho que lo hicieras ejecutable y grafico con las tablas ^^ . aunque el metodo `de colegio` es el que ya hizo antes @cnbandicoot – lois6b Mar 01 '17 at 11:51
  • 3
    confieso que miré la soución de @cnbandicoot y no entendí absolutamente nada así que me puse a pensarla desde cero. Por eso dicen que grandes mentes piensan igual :D – ffflabs Mar 01 '17 at 11:53
  • si, admito que tu codigo es más claro a golpe de vista pero la idea es la misma. y auqnue en codewars no puedo, es buena su recomendacion de librerias externas. HAHAHA grandes mentes XD – lois6b Mar 01 '17 at 11:55
0

Hoy se resuelve con el nuevo tipo BigInt(), no obstante son mucho màs didàcticas las soluciones antes propuestas.

function sumStrings (a,b){

let stra = BigInt(a);
let strb = BigInt(b);


if (isNaN(a)|| isNaN(b)){
    return (stra.toString(), strb.toString());
}else {
  
    let res = ((stra + strb).toLocaleString());
    res = res.replace(/\./g,'').replace(/,/g , '');
    return res;

}