Я был ошеломлен, узнав, что нет? немедленный способ получить мантиссу/показатель степени CGFloat в Swift.
(Обратите внимание, что мне нужны два значения, а НЕ строковое представление в формате научной записи.)
Эти безнадежно недокументированные свойства Double.expose , Double.significand находятся в Double, но это сильно отличается от желания иметь «обычную, человеческую» мантиссу/показатель степени. (Если я чего-то сильно не упускаю.)
Я напечатал функцию для этого, но это чушь, по крайней мере, по трем основным причинам, которые приходят на ум.
Есть ли решение этой загадки?
(*) Удивлен, что здесь нет 100 вопросов по этой проблеме!
extension CGFloat { // (ignored negatives for clarity)
var decomp: (CGFloat, CGFloat) {
var exponent: CGFloat = 0.0
var mantissa: CGFloat = self
while mantissa < 1.0 {
mantissa = mantissa * 10.0
exponent -= 1
}
while mantissa >= 10.0 {
mantissa = mantissa / 10.0
exponent += 1
}
print("check ... ", self, "\(mantissa)E\(exponent)")
return (mantissa, exponent)
}
}
следовательно ..
var x: CGFloat = 0.00123
print( "yo" , x.decomp)
x = 12
print( "yo" , x.decomp)
x = 1000
print( "yo" , x.decomp)
check ... 0.00123 1.23E-3.0 yo (1.23, -3.0) check ... 12.0 1.2E1.0 yo (1.2, 1.0) check ... 1000.0 1.0E3.0 yo (1.0, 3.0)
(†) Minor - I return the exp as a float since that seemed more consistent for the typical ways you'd then use such a thing, but IDK.





Свойства exponent и significandDouble дают вам двоичную мантиссу и показатели степени. В конце концов, Double — это двоичный тип с плавающей запятой.
Похоже, вы хотите получить показатель степени и мантиссу по основанию 10. Для этого вам следует использовать Decimal, который представляет число по основанию 10.
extension CGFloat {
// this does not consider "special" values like infinities and NaNs
var base10ExponentAndSignificand: (CGFloat, CGFloat) {
let decimal = Decimal(self)
return (
CGFloat(truncating: decimal.significand as NSNumber) * (decimal.isSignMinus ? -1 : 1),
CGFloat(decimal.exponent)
)
}
}
Это даст целые числа n, m, где исходное число равно n * pow(10, m).
Научное обозначение обычно записывается как nEm, где n — число от 1 до 10. Если вы хотите, чтобы n было от 1 до 10, я бы просто использовал log10 для вычисления показателя степени вместо подхода цикла, показанного в вашем вопросе:
extension CGFloat {
// this does not consider "special" values like infinities and NaNs
var base10ExponentAndSignificand: (CGFloat, CGFloat) {
guard self != 0 else { return (0, 0) }
let exp = log10(self.magnitude).rounded(.down)
let significand = self / pow(10, exp)
return (significand, exp)
}
}
@Fattie Вот как они реализовали Decimal.init(Double) в Swift-corelibs-foundation. Он использует циклы, аналогичные тем, которые вы используете. Не уверен, что это поможет.
Вот посмотрите на результаты трех способов сделать это:
Имейте в виду, что мантисса часто «неправильная», отклоняясь на несколько цифр. Это относится ко всем трем методам. :/
0,23
using 'division' method
(mantissa: 2.3000000000000003, exponent: -1.0)
using log method
(normalizedSignificand: 2.3, normalizedExponent: -1.0)
via Decimal method (gives 'random' pairs, not scientific notation pairs)
(notNormalizedSignificand: 2.3000000000000005e+18, notNormalizedExponent: -19.0)
using sprintf %E
2.300000E-01
0.000357
using 'division' method
(mantissa: 3.5700000000000003, exponent: -4.0)
using log method
(normalizedSignificand: 3.57, normalizedExponent: -4.0)
via Decimal method (gives 'random' pairs, not scientific notation pairs)
(notNormalizedSignificand: 357.0, notNormalizedExponent: -6.0)
using sprintf %E
3.570000E-04
0.000857
using 'division' method
(mantissa: 8.57, exponent: -4.0)
using log method
(normalizedSignificand: 8.57, normalizedExponent: -4.0)
via Decimal method (gives 'random' pairs, not scientific notation pairs)
(notNormalizedSignificand: 857.0, notNormalizedExponent: -6.0)
using sprintf %E
8.570000E-04
0.000127
using 'division' method
(mantissa: 1.27, exponent: -4.0)
using log method
(normalizedSignificand: 1.27, normalizedExponent: -4.0)
via Decimal method (gives 'random' pairs, not scientific notation pairs)
(notNormalizedSignificand: 127.0, notNormalizedExponent: -6.0)
using sprintf %E
1.270000E-04
66.0
using 'division' method
(mantissa: 6.6, exponent: 1.0)
using log method
(normalizedSignificand: 6.6, normalizedExponent: 1.0)
via Decimal method (gives 'random' pairs, not scientific notation pairs)
(notNormalizedSignificand: 66.0, notNormalizedExponent: 0.0)
using sprintf %E
6.600000E+01
0.2
using 'division' method
(mantissa: 2.0, exponent: -1.0)
using log method
(normalizedSignificand: 2.0, normalizedExponent: -1.0)
via Decimal method (gives 'random' pairs, not scientific notation pairs)
(notNormalizedSignificand: 2.0, notNormalizedExponent: -1.0)
using sprintf %E
2.000000E-01
0.6
using 'division' method
(mantissa: 6.0, exponent: -1.0)
using log method
(normalizedSignificand: 5.999999999999999, normalizedExponent: -1.0)
via Decimal method (gives 'random' pairs, not scientific notation pairs)
(notNormalizedSignificand: 6.0, notNormalizedExponent: -1.0)
using sprintf %E
6.000000E-01
0.8
using 'division' method
(mantissa: 8.0, exponent: -1.0)
using log method
(normalizedSignificand: 8.0, normalizedExponent: -1.0)
via Decimal method (gives 'random' pairs, not scientific notation pairs)
(notNormalizedSignificand: 8.0, notNormalizedExponent: -1.0)
using sprintf %E
8.000000E-01
Похоже, что использование %E с форматировщиком строк волшебным образом «работает» во всех случаях. Остальные три не «работают».
То есть вам просто не нравятся такие незначительные ошибки, как 0,0000000000000003? Это просто потому, что математика с плавающей запятой не работает . Подход Decimal имеет ту же проблему (но, возможно, реже) для некоторых конкретных значений (попробуйте 0,23). Вы никогда не сможете точно преобразовать двоичный код в десятичный для всех чисел. Это также причина, по которой инициализатор Decimal.init(String) предпочтительнее того, который принимает Double.
«Значит, вам просто не нравятся мелкие ошибки типа: «Конечно, я имею в виду именно это». Есть множество случаев, когда этого следует избегать. И да, я с грустью осознаю, что математика с плавающей запятой - это проклятие жизни :) Ах, спасибо за указание на то, что десятичная дробь действительно та же проблема, я не знал - отредактировал это демо.
Я только что провел быстрый тест, и действительно, %E, похоже, всегда «работает». Интересно, они очень просто округляют до 6 мест или есть еще какой-нибудь трюк!
Критерием, вероятно, является «столько цифр, сколько необходимо, чтобы отличить это число от его соседей (плюс/минус 1 ulp)». См., например, Java Double.toString. docs.oracle.com/javase%2F8%2Fdocs%2Fapi%2F%2F/java/lang/…
Боже мой! По сути, секрет в том, чтобы использовать
Decimal, а неDouble!! Я просто не подумал об этом! Думаю, в названии «Decimal» было что-то хитрое, что меня сбило с толку :)