Резюме
Арифметика с плавающей точкой точный, к сожалению, он не очень хорошо сочетается с нашим обычным представлением числа base-10, так что получается, что мы часто даем ему ввод, который немного от того, что мы написали.
Даже простые числа, такие как 0.01, 0.02, 0.03, 0.04 ... 0.24, не представляются точно как двоичные дроби, даже если в мантиссе были тысячи бит точности, даже если у вас были миллионы. Если вы отсчитываете с шагом 0,01, пока вы не достигнете 0,25, вы получите первую фракцию (в этой последовательности), представленную в base10 и base2. Но если вы попытались использовать FP, ваш 0,01 был бы слегка отключен, поэтому единственный способ добавить 25 из них до хорошего точного 0.25 потребовал бы длинной цепи причинности, включающей защитные биты и округление.
Мы постоянно даем аппарату FP что-то вроде простого в базе 10, но это повторяющаяся фракция в базе 2.
Как это произошло?
Когда мы пишем в десятичной форме, каждая дробь является рациональным числом форма
& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; x / (2n + 5n).
В двоичном выражении мы получаем только член 2n , то есть:
& nbsp ; & NBSP; & NBSP; & NBSP; & NBSP; & NBSP; & NBSP; & NBSP; & NBSP; & NBSP; x / 2n
Итак, в десятичной форме мы не можем представлять 1/3. Поскольку база 10 включает в себя 2 как простой коэффициент, каждое число, которое мы можем записать как двоичную дробь , также может быть записано в виде базовой дроби. Однако вряд ли что-либо, что мы пишем как base10, представляется в двоичном виде. В диапазоне от 0,01, 0,02, 0,03 ... 0,99 только цифры три могут быть представлены в нашем формате FP: 0,25, 0,50 и 0,75, поскольку они равны 1/4, 1/2, и 3/4 - все числа с простым множителем, использующим только 2n-член.
В базе 10 мы не можем представлять 1/3. Но в двоичном коде мы не можем делать 1/10 или 1/3.
Так что, хотя каждая двоичная дробь может быть записана в десятичной системе, обратное неверно. И фактически большинство десятичных дробей повторяются в двоичном формате.
Работа с ним
Разработчикам обычно дают указание & Lt; epsilon , лучшим советом может быть округление до целочисленных значений (в библиотеке C: round () и roundf (), т. е. оставаться в формате FP), а затем сравнивать. Округление до определенной длины десятичной дроби решает большинство проблем с выходом.
Кроме того, при реальных проблемах с хрустом (проблемы, которые FP был изобретен на ранних, ужасно дорогих компьютерах) физические константы Вселенной и все другие измерения известны только относительно небольшому числу значимых цифр, поэтому все пространство проблем было «неточным» в любом случае. FP «точность» не является проблемой в этом виде приложений.
Вся проблема действительно возникает, когда люди пытаются использовать FP для подсчета бобов. Это работает для этого, но только если вы придерживаетесь интегральных значений, какой вид поражает смысл его использования. Вот почему у нас есть все эти библиотеки программного обеспечения с десятичной дроби.
Мне нравится ответ на пиццу от Chris , потому что он описывает фактическую проблему, а не только обычная ручная работа о «неточности». Если бы FP были просто «неточными», мы могли бы исправить , что и сделали бы это несколько десятилетий назад. Причина, по которой у нас нет, - это то, что формат FP компактен и быстр, и это лучший способ хрустить множество чисел. Кроме того, это наследие космической эры и гонки вооружений и ранние попытки решить большие проблемы с очень медленными компьютерами с использованием небольших систем памяти. (Иногда отдельные магнитные сердечники для 1-битного хранилища, но это другая история. )
Заключение
Если вы просто считаете бобы в банке, программные решения, которые используют представления десятичной строки, в первую очередь работают отлично. Но вы не можете делать квантовую хромодинамику или аэродинамику таким образом.
Пошел на этот вопрос во время лекции по Лямбдасу, которая использовала Фибоначчи в качестве возможного варианта использования.
Вы можете сделать рекурсивную лямбду следующим образом:
import java.util.function.Function;
public class Fib {
static Function<Integer, Integer> fib;
public static void main(String[] args) {
fib = (n) -> { return n > 1 ? fib.apply(n-1) + fib.apply(n-2) : n; };
for(int i = 0; i < 10; i++){
System.out.println("fib(" + i + ") = " + fib.apply(i));
}
}
}
Что вы нужно иметь в виду?
Я обычно использую (один раз для всех функциональных интерфейсов) общий вспомогательный класс, который обертывает переменную типа функционального интерфейса. Этот подход решает проблему с локальной инициализацией переменных и позволяет коду выглядеть более четко.
В случае возникновения этого вопроса код будет выглядеть следующим образом:
// Recursive.java
// @param <I> - Functional Interface Type
public class Recursive<I> {
public I func;
}
// Test.java
public double factorial(int n) {
Recursive<IntToDoubleFunction> recursive = new Recursive<>();
recursive.func = x -> (x == 0) ? 1 : x * recursive.func.applyAsDouble(x - 1);
return recursive.func.applyAsDouble(n);
}
Вы можете определить рекурсивную лямбду в качестве переменной экземпляра или класса:
static DoubleUnaryOperator factorial = x -> x == 0 ? 1
: x * factorial.applyAsDouble(x - 1);
, например:
class Test {
static DoubleUnaryOperator factorial = x -> x == 0 ? 1
: x * factorial.applyAsDouble(x - 1));
public static void main(String[] args) {
System.out.println(factorial.applyAsDouble(5));
}
}
печатает 120.0
.
Я слышал в JAX в этом году, что «lambads не поддерживают рекурсию». Под этим утверждением подразумевается, что «это» внутри лямбда всегда относится к окружающему классу.
Но мне удалось определить - по крайней мере, как я понимаю термин «рекурсия» - рекурсивная лямбда и он выглядит следующим образом:
interface FacInterface {
int fac(int i);
}
public class Recursion {
static FacInterface f;
public static void main(String[] args)
{
int j = (args.length == 1) ? new Integer(args[0]) : 10;
f = (i) -> { if ( i == 1) return 1;
else return i*f.fac( i-1 ); };
System.out.println( j+ "! = " + f.fac(j));
}
}
Сохраните это внутри файла «Recursion.java» и с двумя командами «javac Recursion.java» и «java Recursion» это сработало для меня.
Clou должен поддерживать интерфейс, который лямбда должна реализовать как переменная поля в окружающем классе. Лямбда может ссылаться на это поле, и поле не будет неявным окончательным.
public class Main {
static class Wrapper {
Function<Integer, Integer> f;
}
public static void main(String[] args) {
final Wrapper w = new Wrapper();
w.f = x -> x == 0 ? 1 : x * w.f.apply(x - 1);
System.out.println(w.f.apply(10));
}
}
Еще один рекурсивный факториал с Java 8
public static int factorial(int i) {
final UnaryOperator<Integer> func = x -> x == 0 ? 1 : x * factorial(x - 1);
return func.apply(i);
}
Если вам нужно часто делать такие вещи, другой способ - создать вспомогательный интерфейс и метод:
public static interface Recursable<T, U> {
U apply(T t, Recursable<T, U> r);
}
public static <T, U> Function<T, U> recurse(Recursable<T, U> f) {
return t -> f.apply(t, f);
}
И затем напишите:
Function<Integer, Double> fact = recurse(
(i, f) -> 0 == i ? 1 : i * f.apply(i - 1, f));
(В то время как я делал это в общих чертах с ссылочными типами, вы также можете создавать примитивные версии).
Это заимствует из старого трюка в Little Lisper для создания неназванных функций.
Я не уверен, что когда-либо сделаю это в производственном коде, но это интересно ...
Вот решение, которое не полагается на побочный эффект. Чтобы сделать цель интересной, предположим, что вы хотите абстрагироваться от рекурсии (в противном случае решение поля экземпляра вполне допустимо). Хитрость заключается в использовании анонимного класса для получения ссылки «this»:
public static IntToLongFunction reduce(int zeroCase, LongBinaryOperator reduce) {
return new Object() {
IntToLongFunction f = x -> x == 0
? zeroCase
: reduce.applyAsLong(x, this.f.applyAsLong(x - 1));
}.f;
}
public static void main(String[] args) {
IntToLongFunction fact = reduce(1, (a, b) -> a * b);
IntToLongFunction sum = reduce(0, (a, b) -> a + b);
System.out.println(fact.applyAsLong(5)); // 120
System.out.println(sum.applyAsLong(5)); // 15
}
Следующие действия работают, но это кажется тайным.
import java.util.function.Function;
class recursion{
Function<Integer,Integer> factorial_lambda; // The positions of the lambda declaration and initialization must be as is.
public static void main(String[] args) { new recursion();}
public recursion() {
factorial_lambda=(i)->{
if(i==1)
return 1;
else
return i*(factorial_lambda.apply(i-1));
};
System.out.println(factorial_lambda.apply(5));
}
}
// Output 120
У меня нет компилятора Java8, поэтому я не могу проверить свой ответ. Но будет ли это работать, если вы определили переменную «факт» как окончательную?
final IntToDoubleFunction fact = x -> {
return ( x == 0)?1:x* fact.applyAsDouble(x-1);
};
Вы можете создать рекурсивную функцию с помощью этого класса:
public class Recursive<I> {
private Recursive() {
}
private I i;
public static <I> I of(Function<RecursiveSupplier<I>, I> f) {
Recursive<I> rec = new Recursive<>();
RecursiveSupplier<I> sup = new RecursiveSupplier<>();
rec.i = f.apply(sup);
sup.i = rec.i;
return rec.i;
}
public static class RecursiveSupplier<I> {
private I i;
public I call() {
return i;
}
}
}
И тогда вы можете использовать любой функциональный интерфейс всего в 1 строке, используя лямбда и определение вашего функционального интерфейса, как показано ниже:
Function<Integer, Integer> factorial = Recursive.of(recursive ->
x -> x == 0 ? 1 : x * recursive.call().apply(x - 1));
System.out.println(factorial.apply(5));
Я нашел это очень интуитивно понятным и простым в использовании.
public class LambdaExperiments {
@FunctionalInterface
public interface RFunction<T, R> extends Function<T, R> {
R recursiveCall(Function<? super T, ? extends R> func, T in);
default R apply(T in) {
return recursiveCall(this, in);
}
}
@FunctionalInterface
public interface RConsumer<T> extends Consumer<T> {
void recursiveCall(Consumer<? super T> func, T in);
default void accept(T in) {
recursiveCall(this, in);
}
}
@FunctionalInterface
public interface RBiConsumer<T, U> extends BiConsumer<T, U> {
void recursiveCall(BiConsumer<T, U> func, T t, U u);
default void accept(T t, U u) {
recursiveCall(this, t, u);
}
}
public static void main(String[] args) {
RFunction<Integer, Integer> fibo = (f, x) -> x > 1 ? f.apply(x - 1) + f.apply(x - 2) : x;
RConsumer<Integer> decreasingPrint = (f, x) -> {
System.out.println(x);
if (x > 0) f.accept(x - 1);
};
System.out.println("Fibonnaci(15):" + fibo.apply(15));
decreasingPrint.accept(5);
}
}
Во время моих тестов это лучшее, что я мог бы достичь для локальных рекурсивных лямбдов. Они также могут использоваться в потоках, но мы теряем легкость печати цели.
Локальные и анонимные классы, а также lambdas, фиксируют локальные переменные по значению при их создании. Поэтому им невозможно ссылаться на себя, захватывая локальную переменную, потому что значение для указания на себя не существует еще в момент их создания.
Код в локальных и анонимных классах может все еще ссылайтесь на себя, используя this
. Однако this
в лямбда не относится к лямбда; он ссылается на this
из внешней области.
Вместо этого вы можете захватить изменчивую структуру данных, например массив:
IntToDoubleFunction[] foo = { null };
foo[0] = x -> { return ( x == 0)?1:x* foo[0].applyAsDouble(x-1);};
, хотя вряд ли это изящное решение.
Один из способов - записать вторичную функцию, helper
, которая принимает в качестве аргументов функцию и число, а затем записывает нужную функцию fact = helper(helper,x)
.
Так же:
BiFunction<BiFunction, Double, Double> factHelper =
(f, x) -> (x == 0) ? 1.0 : x*(double)f.apply(f,x-1);
Function<Double, Double> fact =
x -> factHelper.apply(factHelper, x);
Мне кажется, что это немного более элегантно, чем полагаться на семантику углового случая, такую как замыкание, которое фиксирует ссылку на изменяемую структуру или позволяет саморегуляцию с предупреждением о возможности «могущества не будет инициализировано ».
Тем не менее, это не идеальное решение из-за системы типов Java - генераторы не могут гарантировать, что f
, аргумент factHelper
, имеет тот же тип, что и factHelper
(т. е. те же типы ввода и типы вывода), поскольку это будет бесконечно вложенным общим.
Таким образом, вместо этого более безопасным решением может быть:
Function<Double, Double> fact = x -> {
BiFunction<BiFunction, Double, Double> factHelper =
(f, d) -> (d == 0) ? 1.0 : d*(double)f.apply(f,d-1);
return factHelper.apply(factHelper, x);
};
Запах кода понесенный из factHelper
менее совершенного общего типа, теперь содержится (или, осмелюсь сказать, инкапсулирован) внутри лямбда, гарантируя, что factHelper
никогда не будет вызван неосознанно.
Учитывая тот факт, что «this» в лямбда относится к содержащему классу, следующие компиляции без ошибок (с добавленными зависимостями, конечно):
public class MyClass {
Function<Map, CustomStruct> sourceToStruct = source -> {
CustomStruct result;
Object value;
for (String key : source.keySet()) {
value = source.get(key);
if (value instanceof Map) {
value = this.sourceToStruct.apply((Map) value);
}
result.setValue(key, value);
}
return result;
};
}
Немного похоже на самый первый ответ ...
public static Function<Integer,Double> factorial;
static {
factorial = n -> {
assert n >= 0;
return (n == 0) ? 1.0 : n * factorial.apply(n - 1);
};
}
Проблема заключается в том, что лямбда-функции хотят работать с переменными final
, тогда как нам нужна изменчивая Function
-референция, которая может быть заменена нашей лямбдой.
Самый простой трюк появляется чтобы определить переменную как переменную-член, и компилятор не будет жаловаться.
Я изменил свой пример, чтобы использовать IntUnaryOperator
вместо IntToDoubleFunction
, так как мы просто работаем над Integers
в любом случае здесь.
import org.junit.Test;
import java.util.function.IntUnaryOperator;
import static org.junit.Assert.assertEquals;
public class RecursiveTest {
private IntUnaryOperator operator;
@Test
public void factorialOfFive(){
IntUnaryOperator factorial = factorial();
assertEquals(factorial.applyAsInt(5), 120); // passes
}
public IntUnaryOperator factorial() {
return operator = x -> (x == 0) ? 1 : x * operator.applyAsInt(x - 1);
}
}
Другая версия с использованием аккумулятора, так что рекурсия может быть оптимизирована. Переведено в определение общего интерфейса.
Function<Integer,Double> facts = x -> { return ( x == 0)?1:x* facts.apply(x-1);};
BiFunction<Integer,Double,Double> factAcc= (x,acc) -> { return (x == 0)?acc:factAcc.apply(x- 1,acc*x);};
Function<Integer,Double> fact = x -> factAcc.apply(x,1.0) ;
public static void main(String[] args) {
Test test = new Test();
test.doIt();
}
public void doIt(){
int val=70;
System.out.println("fact(" + val + ")=" + fact.apply(val));
}
}
Вы также можете определить его как локальную переменную, создав последний массив размера один (например, Function []), а затем назначьте функцию элементу 0. Сообщите мне, если вам нужен точный синтаксис