Numero di uguaglianza / astrazione dalla precisione e rappresentazione

1

Cerco di trovare una soluzione per uscire da questi gravi problemi di confronto dei numeri in Java ... almeno per i numeri in un certo intervallo con una certa precisione con un errore di rappresentazione.

L'attività consiste nell'implementare un controllo di uguaglianza per i numeri che implementano l'interfaccia numero del pacchetto java.lang. Anche qui: limiterei il controllo di uguaglianza ai tipi ovvi: byte, short, intero, long, float, double e i loro equivalenti oggetto. Ovviamente dovrebbero essere gestiti anche BigInteger e BigDecimal.

La mia prima idea era quella di normalizzare tutti i numeri con un doppio valore. Ho trovato il seguente:

public class MyNumber {


    private double raw;


    public MyNumber(Number raw) {
        this.raw = Double.parseDouble(raw.toString());
    }


    @Override
    public int hashCode() {
        return new Double(this.raw).hashCode();
    }


    @Override
    public boolean equals(Object obj) {

        if (obj instanceof MyNumber) {

            MyNumber that = (MyNumber) obj;

            return Double.toString(that.raw).equals(Double.toString(this.raw));

        }

        return false;
    }


}

Non cerco la fantasia per tutte le soluzioni di copertura. Una soluzione che mi tiene lontano dalle sciocchezze tecniche spiegabili ma semantiche di ...

    BigDecimal x = new BigDecimal("1");
    BigDecimal y = new BigDecimal("1.00");
    Assert.assertFalse(x.equals(y));

Ho scritto alcuni casi di test per mostrare cosa intendo:

public class TestMyNumber {


    /**
     * Small numbers are equal if they are semantically equal.
     */
    @Test
    public void test1() {

        MyNumber myNumber1 = new MyNumber(0.000000000000001f);
        MyNumber myNumber2 = new MyNumber(new BigDecimal("0.000000000000001"));

        Assert.assertEquals(myNumber1, myNumber2);

    }


    /**
     * Big numbers are seen as equal if they are semantically equal.
     */
    @Test
    public void test3() {

        MyNumber myNumber1 = new MyNumber(999999999999999.9);
        MyNumber myNumber2 = new MyNumber(new BigDecimal("999999999999999.9"));

        Assert.assertEquals(myNumber1, myNumber2);

    }


    /**
     * Big numbers are seen as equal if they are semantically equal.
     */
    @Test
    public void test4() {

        MyNumber myNumber1 = new MyNumber(999999999999999.0);
        MyNumber myNumber2 = new MyNumber(999999999999999l);

        Assert.assertEquals(myNumber1, myNumber2);

    }


    /**
     * Really big numbers are equal. Error is acceptable.
     */
    @Test
    public void test5() {

        MyNumber myNumber1 = new MyNumber(544785684365874268756.1);
        MyNumber myNumber2 = new MyNumber(new BigDecimal("544785684365874268756.9"));

        Assert.assertEquals(myNumber1, myNumber2);

    }


}

Il mio obiettivo principale è quello di astrarre dalla precisione e dalla rappresentazione. Almeno entro un raggio. La velocità è nulla a cui tengo attualmente.

Hai qualche suggerimento per raggiungere questa astrazione?

Prossimo approccio:

public class MyNumber {


    private BigDecimal raw;


    public MyNumber(Number raw) {
        this.raw = new BigDecimal(Double.parseDouble(raw.toString())).stripTrailingZeros();
    }


    @Override
    public int hashCode() {
        return this.raw.hashCode();
    }


    @Override
    public boolean equals(Object obj) {

        if (obj instanceof MyNumber) {

            MyNumber that = (MyNumber) obj;

            return that.raw.equals(this.raw);

        }

        return false;
    }


}

Note:

I numeri semanticamente uguali sono numeri che puoi scambiare in qualsiasi operazione tu voglia e non sai se sono stati scambiati affatto perché il risultato rimane lo stesso. Il punto è: 2 è uguale a 2.0 è uguale a 2.000000000.

MyNumbers non sarà mai il risultato di un'operazione solo un parametro di input.

Un numero dovrebbe poter essere controllato per l'uguaglianza. Non voglio preoccuparmi della rappresentazione tecnica.

    
posta oopexpert 06.12.2016 - 23:20
fonte

1 risposta

2

Non è possibile normalizzare tutti i numeri su un valore in virgola mobile.

I numeri sono un problema più complicato di quanto sembri riconoscere. Non si tratta solo di "Java con grossi problemi", anche se gestire i numeri in Java è complicato.

Esiste una varietà di tipi di numeri fondamentalmente diversi: numeri interi, numeri decimali, numeri in virgola mobile, frazioni, numeri reali, numeri complessi ecc. Alcuni numeri di tipi diversi non sono matematicamente compatibili tra loro. Ad esempio, non esiste una rappresentazione intera per il numero decimale 0,1. Non esiste nemmeno una rappresentazione in virgola mobile binario.

Non è strettamente possibile scrivere un metodo uguale per due numeri qualsiasi di tipi diversi. Questo perché il numero della classe non contiene alcuna funzionalità o contratto per il controllo dell'uguaglianza. Chiunque può creare una sottoclasse con qualsiasi definizione personalizzata di uguaglianza per gli oggetti di quella particolare sottoclasse.

Tuttavia, è possibile scrivere un metodo che sia in grado di confrontare ogni numero attualmente fornito da Java. A tale scopo è possibile normalizzare i numeri su BigDecimals e quindi utilizzare il metodo compareTo della classe BigDecimal. Non è molto efficiente ma dovrebbe funzionare.

Rispondi a un commento qui sotto:

I tentativi di definire l'uguaglianza considerando i numeri vicini gli uni agli altri porteranno a problemi. Almeno se stai cercando di ottenere un'equivalenza adeguata con la proprietà transitiva.

Ad esempio, c'è una quantità infinita di numeri decimali attorno a un numero in virgola mobile che è più lontano da qualsiasi altro numero in virgola mobile della stessa precisione. Se sono considerati uguali a quel numero in virgola mobile "centrale", allora per proprietà transitiva dovrebbero essere considerati uguali tra loro.

Prendere in considerazione gli interi porta ad una definizione ancora più ampia di uguaglianza. Usando la regola di arrotondamento a metà, il doppio esatto 0,5 sarebbe uguale all'intero 1 e qualche decimale di 0,5 - ε che sarebbe uguale all'intero 0. Per la proprietà transitiva 0 sarebbe uguale a 1. Tutti i numeri tra 0 e 1 sarebbero uguaglia pure.

    
risposta data 07.12.2016 - 16:13
fonte

Leggi altre domande sui tag