Как использовать общий тип FloatingPoint для Float/Double

Я хотел бы, чтобы приведенная ниже функция работала как со значениями Float, так и с Double:

func srgb2linear(_ S: Float) -> Float {
  if S <= 0.04045 {
    return S / 12.92
  } else {
    return pow((S + 0.055) / 1.055, 2.4)
  }
}

Документация по Swift 4 говорит, что мне нужен FloatingPoint универсальный, чтобы представлять как Float и двойные классы, например:

func srgb2linear<T: FloatingPoint>(_ S: T) -> T

Однако, когда я пытаюсь это сделать, он не компилируется со следующими ошибками:

Error: binary operator '<=' cannot be applied to operands of type 'T' and 'Double'
Error: binary operator '/' cannot be applied to operands of type 'T' and 'Double'
Error: binary operator '+' cannot be applied to operands of type 'T' and 'Double'

Как возможно, что для универсального представления чисел с плавающей запятой такие операторы не реализованы? А если не так, как я могу написать эту функцию в Swift?


person hyperknot    schedule 13.02.2018    source источник
comment
Одна проблема заключается в том, что 12.92 и т. д. не распознаются как литералы с плавающей запятой, что можно решить, используя вместо этого BinaryFloatingPoint (сравните stackoverflow.com/a/48021458 /1187415). – Другая проблема заключается в том, что не существует универсальной функции pow.   -  person Martin R    schedule 13.02.2018
comment
Я прочитал ваш другой ответ, а также SE-0067, но я не понимаю, зачем здесь нужно использовать BinaryFloatingPoint. В нем говорится, что FP соответствует Comparable и что BFP добавляет некоторые дополнительные операции, которые имеют смысл только для фиксированной системы счисления.   -  person hyperknot    schedule 13.02.2018
comment
Проблема не в сравнении, а в буквальных константах 0.04045 и т. д., а также в функции pow.   -  person Martin R    schedule 13.02.2018


Ответы (2)


Одна проблема заключается в том, что FloatingPoint не является подпротоколом ExpressibleByFloatLiteral, поэтому ваши литералы с плавающей запятой не обязательно могут быть преобразованы в T. Вы можете решить эту проблему либо изменив FloatingPoint на BinaryFloatingPoint (который является подпротоколом ExpressibleByFloatLiteral), либо добавив ExpressibleByFloatLiteral в качестве отдельного требования.

Тогда вы столкнетесь с проблемой, что нет функции pow, которая является общей для FloatingPoint, и нет члена FloatingPoint или BinaryFloatingPoint, который выполняет возведение в степень. Вы можете решить эту проблему, создав новый протокол и согласовав с ним существующие типы с плавающей запятой:

protocol Exponentiatable {
    func toPower(_ power: Self) -> Self
}

extension Float: Exponentiatable {
    func toPower(_ power: Float) -> Float { return pow(self, power) }
}

extension Double: Exponentiatable {
    func toPower(_ power: Double) -> Double { return pow(self, power) }
}

extension CGFloat: Exponentiatable {
    func toPower(_ power: CGFloat) -> CGFloat { return pow(self, power) }
}

Обратите внимание, что существует также тип Float80, но стандартная библиотека не предоставляет для него функцию pow.

Теперь мы можем написать работающую универсальную функцию:

func srgb2linear<T: FloatingPoint>(_ S: T) -> T
    where T: ExpressibleByFloatLiteral, T: Exponentiatable
{
    if S <= 0.04045 {
        return S / 12.92
    } else {
        return ((S + 0.055) / 1.055).toPower(2.4)
    }
}
person rob mayoff    schedule 13.02.2018

Вы можете определить второй с двойными аргументами:

func srgb2linear(_ S: Double) -> Double {
  if S <= 0.04045 {
    return S / 12.92
  } else {
    return pow((S + 0.055) / 1.055, 2.4)
  }
}

или, если преобразования Float‹->Double не вызывают беспокойства:

func srgb2linear(_ S: Double) -> Double {
  return Double(srgb2linear(Float(S)))
}
person Alain T.    schedule 13.02.2018