2014-12-26 20 views
6

IntroduzioneOttenere prossimo numero più piccolo più vicino a un decimale

Per alcuni calcoli ho bisogno di trovare il minor numero possibile posso aggiungere/sottrarre da un numero specificato senza ottenere JavaScript nei guai con il tipo di dati utilizzato interno.

Calcio

ho cercato di scrivere una funzione che è in grado di restituire il numero più vicino accanto al valore nella direzione di valore DIR.

function nextNearest(value, direction) { 
    // Special cases for value==0 or value==direction removed 

    if (direction < value) { 
     return value - Number.MIN_VALUE; 
    } else { 
     return value + Number.MIN_VALUE; 
    } 
} 

Il problema di questo è che JavaScript utilizza un tipo a galleggiante 64 bit (Credo) che ha diverse dimensioni minime step a seconda del suo esponente corrente.

problema in dettaglio

Il problema è la dimensione del passo a seconda della sua attuale esponente:

var a = Number.MIN_VALUE; 

console.log(a); 
// 5e-324 

console.log(a + Number.MIN_VALUE); 
// 1e-323 (changed, as expected) 


var a = Number.MAX_VALUE; 

console.log(a); 
// 1.7976931348623157e+308 

console.log(a - Number.MIN_VALUE); 
// 1.7976931348623157e+308 (that's wrong) 

console.log(a - Number.MIN_VALUE == a); 
// true (which also is wrong) 

Sommario

Così come posso trovare il numero più piccolo possibile che posso aggiungi/sottrai da un valore specificato in un parametro in qualsiasi direzione? In C++ questo sarebbe facilmente possibile accedendo ai valori binari dei numeri.

+3

È possibile usufruire di [array tipizzati] (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays) per arrivare al basso rappresentazione di livello di valori in virgola mobile in JavaScript. Crea un elemento Float64Array a 1 elemento e un array UInt8 a 8 elementi entrambi sullo stesso buffer. – Pointy

risposta

5

Ho cercato di implementare il suggerimento di Pointy dai commenti (utilizzando matrici tipizzate). Questo è liberamente adattato dall'implementazione di glibc di nextafter. Dovrebbe essere abbastanza buono

Si can actually just increment/decrement la rappresentazione in numeri interi a 64 bit di un doppio per ottenere il risultato desiderato. Un overflow di mantissa traboccherà verso l'esponente che capita di essere proprio quello che vuoi.

Poiché JavaScript non fornisce un Uint64Array, ho dovuto implementare un overflow manuale su due interi a 32 bit.

Questo funziona su architetture little-endian, ma ho lasciato fuori big-endian poiché non ho modo di testarlo. Se hai bisogno di questo per lavorare su architetture big-endian dovrai adattare questo codice.

// Return the next representable double from value towards direction 
 
function nextNearest(value, direction) { 
 
    if (typeof value != "number" || typeof direction != "number") 
 
    return NaN; 
 
    
 
    if (isNaN(value) || isNaN(direction)) 
 
    return NaN; 
 
    
 
    if (!isFinite(value)) 
 
    return value; 
 
    
 
    if (value === direction) 
 
    return value; 
 
    
 
    var buffer = new ArrayBuffer(8); 
 
    var f64 = new Float64Array(buffer); 
 
    var u32 = new Uint32Array(buffer); 
 
    
 
    f64[0] = value; 
 
    
 
    if (value === 0) { 
 
    u32[0] = 1; 
 
    u32[1] = direction < 0 ? 1 << 31 : 0; 
 
    } else if ((value > 0) && (value < direction) || (value < 0) && (value > direction)) { 
 
    if (u32[0]++ === 0xFFFFFFFF) 
 
     u32[1]++; 
 
    } else { 
 
    if (u32[0]-- === 0) 
 
     u32[1]--; 
 
    } 
 
    
 
    return f64[0]; 
 
} 
 

 
var testCases = [0, 1, -1, 0.1, 
 
       -1, 10, 42e42, 
 
       0.9999999999999999, 1.0000000000000002, 
 
       10.00000762939453, // overflows between dwords 
 
       5e-324, -5e-324, // minimum subnormals (around zero) 
 
       Number.MAX_VALUE, -Number.MAX_VALUE, 
 
       Infinity, -Infinity, NaN]; 
 

 
document.write("<table><tr><th>n</th><th>next</th><th>prev</th></tr>"); 
 
testCases.forEach(function(n) { 
 
    var next = nextNearest(n, Infinity); 
 
    var prev = nextNearest(n, -Infinity); 
 
    document.write("<tr><td>" + n + "</td><td>" + next + "</td><td>" + prev + "</td></tr>"); 
 
}); 
 
document.write("</table>");

+1

'DataView.getFloat()' ha un parametro 'isLittleEndian' che potrebbe essere d'aiuto. http://www.ecma-international.org/ecma-262/6.0/#sec-dataview.prototype.getfloat64 –

0

Number.MIN_VALUE è il numero rappresentabile il più piccolo possibile, non la minima differenza possibile tra i numeri rappresentabili. A causa del modo in cui javascript gestisce i numeri in virgola mobile, la minima differenza possibile tra i numeri rappresentabili cambia con la dimensione del numero. Man mano che il numero aumenta, la precisione si riduce. Quindi non esiste un numero che risolverà il tuo problema. Ti suggerisco di ripensare a come risolvere il problema o scegliere un sottoinsieme di numeri da utilizzare e non l'intero intervallo di valori MAX e MIN.

ad esempio: 1.7976931348623156e+308 == 1.7976931348623155e+308 //true

Inoltre, sottraendo MIN_VALUE da MAX_VALUE si sta cercando di ottenere javascript per rappresentare accuratamente un numero con più di 600 cifre significative. È un po 'troppo.

+2

Stai solo spiegando il problema dell'OP qui. Per ogni numero finito 'x' c'è un numero più piccolo' e' come 'x + e! = E || x-e! = e'. L'OP sta chiedendo come trovare 'e' per un dato numero' x'. –

+0

Certo, ma non c'è una risposta a questo problema che sembra ragionevole. Fino a Max_Value hai bisogno di almeno 2. Giù al valore minimo se aggiungi 2 ottieni 2. Quindi perdi effettivamente ogni distinzione tra numeri molto piccoli e devi saltare interi con il molto grande. Questo problema non ha una soluzione utile che non includa una scala di valori scorrevole. – greggreg

+0

Sì, è quello che sto cercando di dire. Il mio * numero * 'e' sopra è davvero una funzione ... Scrivi una' funzione epsilon (x) 'come' x + epsilon (x)! = X'. * Epsilon * dipende dalla scala di 'x'. –

Problemi correlati