2015-01-08 3 views
0

Я работаю над интерпретатором lisp и реализую рациональные числа. Я думал, что у них есть преимущество над удвоениями, чтобы иметь возможность представлять числа, такие как 1/3. Я сделал несколько расчетов для сравнения результатов. Я был удивлен результатамидвойная точность и десятичные знаки перидока

с двойниками

(* 3.0 (/ 1.0 3.0)) -> 1 
(* 3.0 (/ 4.0 3.0)) -> 4 
(* 81.0 (/ 1.0 81.0)) -> 1 

с коэффициентами:

(* 3 (/ 1 3)) -> 1 
(* 3 (/ 4 3)) -> 4 
(* 81 (/ 1 81)) -> 1 

Почему результаты операций с плавающей точкой точные? Должна быть потеря точности. двойники не могут хранить бесконечное количество цифр. Или мне что-то не хватает?

Я проверил быструю проверку с помощью небольшого C-приложения. Тот же результат.

#include <stdio.h> 

int main() 
{ 
    double a1 = 1, b1 = 3; 
    double a2 = 1, b2 = 81; 

    printf("result : %f\n", a1/b1 * b1); 
    printf("result : %f\n", a2/b2 * b2); 

    return 0; 
} 

Выход:

результат: 1,000000
результат: 1,000000

MFG

Martin

+0

Попробуйте '% .20f'. Двойные значения точности точны примерно до 15 знаков после запятой, даже если они не точны. –

+0

[Прочитайте это сначала, пожалуйста] (http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html). –

+0

Может ли компилятор заметить деление и умножение и, таким образом, сделать его недействительным (или переупорядочением порядка операции) для вас? – David

ответ

2

В первом случае, точный результат умножения на полпути между 1.0 и самым большим двойным, что меньше 1.0. В соответствии с круговыми правилами IEEE 754, половина номеров округляется до четного, в этом случае до 1.0. По сути, округление результата умножения уменьшает ошибку, возникающую при округлении результата деления.

Эта программа Java иллюстрирует, что происходит. Преобразования в BigDecimal и арифметические операции BigDecimal все точно:

import java.math.BigDecimal; 

public class Test { 
    public static void main(String[] args) { 
    double a1 = 1, b1 = 3; 

    System.out.println("Final Result: " + ((a1/b1) * b1)); 
    BigDecimal divResult = new BigDecimal(a1/b1); 
    System.out.println("Division Result: " + divResult); 
    BigDecimal multiplyResult = divResult.multiply(BigDecimal.valueOf(3)); 
    System.out.println("Multiply Result: " + multiplyResult); 
    System.out.println("Error rounding up to 1.0: " 
     + BigDecimal.valueOf(1).subtract(multiplyResult)); 
    BigDecimal nextDown = new BigDecimal(Math.nextAfter(1.0, 0)); 
    System.out.println("Next double down from 1.0: " + nextDown); 
    System.out.println("Error rounding down: " 
     + multiplyResult.subtract(nextDown)); 
    } 
} 

Выход:

Final Result: 1.0 
Division Result: 0.333333333333333314829616256247390992939472198486328125 
Multiply Result: 0.999999999999999944488848768742172978818416595458984375 
Error rounding up to 1.0: 5.5511151231257827021181583404541015625E-17 
Next double down from 1.0: 0.99999999999999988897769753748434595763683319091796875 
Error rounding down: 5.5511151231257827021181583404541015625E-17 

Выходной сигнал на второй, похоже, случае:

Final Result: 1.0 
Division Result: 0.
Multiply Result: 0.9999999999999999444888487687421729788184165954589843750 
Error rounding up to 1.0: 5.55111512312578270211815834045410156250E-17 
Next double down from 1.0: 0.99999999999999988897769753748434595763683319091796875 
Error rounding down: 5.55111512312578270211815834045410156250E-17 

Это программа иллюстрирует ситуацию, в которой может накапливаться погрешность округления:

import java.math.BigDecimal; 

public class Test { 
    public static void main(String[] args) { 
    double tenth = 0.1; 
    double sum = 0; 
    for (int i = 0; i < 10; i++) { 
     sum += tenth; 
    } 
    System.out.println("Sum: " + new BigDecimal(sum)); 
    System.out.println("Product: " + new BigDecimal(10.0 * tenth)); 
    } 
} 

Выход:

Sum: 0.99999999999999988897769753748434595763683319091796875 
Product: 1 

Умножение на 10 раундов до 1,0. Выполнение такого же умножения путем повторного добавления не дает точного ответа.

+0

Большое спасибо за подробное объяснение. Теперь я понимаю, что происходит. Но я все еще ищу отрицательный пример, когда воспроизведение исходного номера не выполняется. –

+0

@ user3624086 Каковы ваши ограничения для встречного примера? Например, может ли делитель быть меньше 1? –

+0

Его следует сравнивать с расчетом соотношения для визуализации различий. Итак, нет! Делитель должен быть целым числом. Что-то вроде (* 3 (/ 1 3)) vs (* 3.0 (/ 1.0 3.0)). (/ 1 3) дает в качестве интерпретатора рациональный результат 1/3. Вы можете проверить его на remote-lisp.spdns.de. Но сейчас это не очень удобно. Ну, конечно, это может быть сложнее, если это все еще понятно. Мне пока не повезло. Давайте расслабляем контуры. Результатом должно быть любое целое число. –

Смежные вопросы