Округление до произвольного количества значащих цифр

Как вы можете округлить любое число (а не только целые числа> 0) до N значащих цифр?

Например, если я хочу округлить до трех значащих цифр, я ищу формулу, которая может принять:

1,239,451 и вернуть 1,240,000

12.1257 и вернуть 12.1

.0681 и return .0681

5 и вернуть 5

Естественно, что алгоритм не должен быть жестко закодирован, чтобы обрабатывать только N из 3, хотя это было бы началом.

Вот тот же код на Java без ошибки 12.100000000000001, другие ответы есть

Я также удалил повторяющийся код, изменил power на целое число, чтобы предотвратить плавающие проблемы при выполнении n - d , и сделал длинный промежуток более понятным

Ошибка была вызвана умножением большого числа на небольшое число. Вместо этого я делю два числа одинакового размера.

РЕДАКТИРОВАТЬ
Исправлено больше ошибок. Добавлена ​​проверка на 0, так как это приведет к NaN. Фактически эта функция работает с отрицательными числами (исходный код не обрабатывает отрицательные числа, потому что журнал отрицательного числа является сложным числом)

 public static double roundToSignificantFigures(double num, int n) { if(num == 0) { return 0; } final double d = Math.ceil(Math.log10(num < 0 ? -num: num)); final int power = n - (int) d; final double magnitude = Math.pow(10, power); final long shifted = Math.round(num*magnitude); return shifted/magnitude; } 

Вот короткая и приятная реализация JavaScript:

 function sigFigs(n, sig) { var mult = Math.pow(10, sig - Math.floor(Math.log(n) / Math.LN10) - 1); return Math.round(n * mult) / mult; } alert(sigFigs(1234567, 3)); // Gives 1230000 alert(sigFigs(0.06805, 3)); // Gives 0.0681 alert(sigFigs(5, 3)); // Gives 5 

РЕЗЮМЕ:

 double roundit(double num, double N) { double d = log10(num); double power; if (num > 0) { d = ceil(d); power = -(dN); } else { d = floor(d); power = -(dN); } return (int)(num * pow(10.0, power) + 0.5) * pow(10.0, -power); } 

Таким образом, вам нужно найти десятичное место для первой ненулевой цифры, а затем сохранить следующие цифры N-1, а затем округлить N-й разряд, основываясь на остальном.

Мы можем использовать журнал для первого.

 log 1239451 = 6.09 log 12.1257 = 1.08 log 0.0681 = -1.16 

Таким образом, для чисел> 0 возьмите верхнюю часть журнала. Для чисел <0 возьмите пол журнала.

Теперь мы имеем цифру d : 7 в первом случае, 2 во 2-м, -2 в 3-м.

Мы должны округлить цифру (dN) . Что-то вроде:

 double roundedrest = num * pow(10, -(dN)); pow(1239451, -4) = 123.9451 pow(12.1257, 1) = 121.257 pow(0.0681, 4) = 681 

Затем выполните стандартное округление:

 roundedrest = (int)(roundedrest + 0.5); 

И отменить власть.

 roundednum = pow(roundedrest, -(power)) 

Где мощность – это мощность, рассчитанная выше.


О точности: ответ Pyrolistical действительно ближе к реальному результату. Но учтите, что вы не можете представить 12.1 точно в любом случае. Если вы напечатаете ответы следующим образом:

 System.out.println(new BigDecimal(n)); 

Ответы:

 Pyro's: 12.0999999999999996447286321199499070644378662109375 Mine: 12.10000000000000142108547152020037174224853515625 Printing 12.1 directly: 12.0999999999999996447286321199499070644378662109375 

Итак, используйте ответ Пиро!

Разве это не «короткая и сладкая» реализация JavaScript

 Number(n).toPrecision(sig) 

например

 alert(Number(12345).toPrecision(3) 

?

Извините, я здесь не занимаюсь, просто использование функции «roundit» от Claudiu и .toPrecision в JavaScript дает мне разные результаты, но только в округлении последней цифры.

JavaScript:

 Number(8.14301).toPrecision(4) == 8.143 

.СЕТЬ

 roundit(8.14301,4) == 8.144 

Решение Pyrolistical’s (очень приятное!) Все еще имеет проблему. Максимальное двойное значение в Java составляет порядка 10 ^ 308, а минимальное значение – порядка 10 ^ -324. Таким образом, вы можете столкнуться с трудностями при применении функции roundToSignificantFigures к чему-то, что находится в пределах нескольких полномочий из десяти Double.MIN_VALUE . Например, когда вы звоните

 roundToSignificantFigures(1.234E-310, 3); 

то переменная power будет иметь значение 3 – (-309) = 312. Следовательно, переменная magnitude станет Infinity , и с тех пор все это мусор. К счастью, это не является непреодолимой проблемой: это только факторная magnitude которая переполнена. Что действительно имеет значение, это num * magnitude product , и это не переполняет. Один из способов решения этой magintude состоит в том, чтобы разбить умножение на magintude фактор на два этапа:

public static double roundToNumberOfSignificantDigits(double num, int n) { final double maxPowerOfTen = Math.floor(Math.log10(Double.MAX_VALUE)); if(num == 0) { return 0; } final double d = Math.ceil(Math.log10(num < 0 ? -num: num)); final int power = n - (int) d; double firstMagnitudeFactor = 1.0; double secondMagnitudeFactor = 1.0; if (power > maxPowerOfTen) { firstMagnitudeFactor = Math.pow(10.0, maxPowerOfTen); secondMagnitudeFactor = Math.pow(10.0, (double) power - maxPowerOfTen); } else { firstMagnitudeFactor = Math.pow(10.0, (double) power); } double toBeRounded = num * firstMagnitudeFactor; toBeRounded *= secondMagnitudeFactor; final long shifted = Math.round(toBeRounded); double rounded = ((double) shifted) / firstMagnitudeFactor; rounded /= secondMagnitudeFactor; return rounded; }
public static double roundToNumberOfSignificantDigits(double num, int n) { final double maxPowerOfTen = Math.floor(Math.log10(Double.MAX_VALUE)); if(num == 0) { return 0; } final double d = Math.ceil(Math.log10(num < 0 ? -num: num)); final int power = n - (int) d; double firstMagnitudeFactor = 1.0; double secondMagnitudeFactor = 1.0; if (power > maxPowerOfTen) { firstMagnitudeFactor = Math.pow(10.0, maxPowerOfTen); secondMagnitudeFactor = Math.pow(10.0, (double) power - maxPowerOfTen); } else { firstMagnitudeFactor = Math.pow(10.0, (double) power); } double toBeRounded = num * firstMagnitudeFactor; toBeRounded *= secondMagnitudeFactor; final long shifted = Math.round(toBeRounded); double rounded = ((double) shifted) / firstMagnitudeFactor; rounded /= secondMagnitudeFactor; return rounded; } 

Как насчет этого Java-решения:

 double roundToSignificantFigure (double num, int precision) {
  вернуть новый BigDecimal (num)
             .round (новый MathContext (точность, RoundingMode.HALF_EVEN))
             .doubleValue (); 
 }

Вот модифицированная версия JavaScript Ates, которая обрабатывает отрицательные числа.

 function sigFigs(n, sig) { if ( n === 0 ) return 0 var mult = Math.pow(10, sig - Math.floor(Math.log(n < 0 ? -n: n) / Math.LN10) - 1); return Math.round(n * mult) / mult; } 

Это произошло на 5 лет позже, но я поделюсь тем, у кого все еще есть одна и та же проблема. Мне это нравится, потому что это просто и никаких вычислений на стороне кода. См. Раздел « Встроенные методы отображения значимых цифр» для получения дополнительной информации.

Это если вы просто хотите распечатать его.

 public String toSignificantFiguresString(BigDecimal bd, int significantFigures){ return String.format("%."+significantFigures+"G", bd); } 

Это если вы хотите его преобразовать:

 public BigDecimal toSignificantFigures(BigDecimal bd, int significantFigures){ String s = String.format("%."+significantFigures+"G", bd); BigDecimal result = new BigDecimal(s); return result; } 

Вот пример этого в действии:

 BigDecimal bd = toSignificantFigures(BigDecimal.valueOf(0.0681), 2); 

Вы пробовали просто кодировать его так, как вы делали это вручную?

  1. Преобразовать число в строку
  2. Начиная с начала строки, цифры счета – ведущие нули не значительны, все остальное.
  3. Когда вы дойдете до цифры «nth», загляните вперед на следующую цифру, и если она равна 5 или выше, округлите вверх.
  4. Замените все конечные цифры нулями.

[Исправлено, 2009-10-26]

По существу, для N значительных дробных цифр:

• Умножьте число на 10 N
• Добавить 0,5
• Усечь цифры долей (т. Е. Усечь результат в целое число)
• Разделить на 10 N

Для N значимых интегральных (дробных) цифр:

• Разделите число на 10 N
• Добавить 0,5
• Усечь цифры долей (т. Е. Усечь результат в целое число)
• Умножить на 10 N

Вы можете сделать это на любом калькуляторе, например, с оператором INT (целочисленное усечение).

 /** * Set Significant Digits. * @param value value * @param digits digits * @return */ public static BigDecimal setSignificantDigits(BigDecimal value, int digits) { //# Start with the leftmost non-zero digit (eg the "1" in 1200, or the "2" in 0.0256). //# Keep n digits. Replace the rest with zeros. //# Round up by one if appropriate. int p = value.precision(); int s = value.scale(); if (p < digits) { value = value.setScale(s + digits - p); //, RoundingMode.HALF_UP } value = value.movePointRight(s).movePointLeft(p - digits).setScale(0, RoundingMode.HALF_UP) .movePointRight(p - digits).movePointLeft(s); s = (s > (p - digits)) ? (s - (p - digits)) : 0; return value.setScale(s); } 

Вот код Pyrolistical’s (в настоящее время верхнего ответа) в Visual Basic.NET, если кому-то это понадобится:

 Public Shared Function roundToSignificantDigits(ByVal num As Double, ByVal n As Integer) As Double If (num = 0) Then Return 0 End If Dim d As Double = Math.Ceiling(Math.Log10(If(num < 0, -num, num))) Dim power As Integer = n - CInt(d) Dim magnitude As Double = Math.Pow(10, power) Dim shifted As Double = Math.Round(num * magnitude) Return shifted / magnitude End Function 

JavaScript:

 Number( my_number.toPrecision(3) ); 

Функция Number изменит выход формы "8.143e+5" на "814300" .

Это тот, который я придумал в VB:

 Function SF(n As Double, SigFigs As Integer) Dim l As Integer = n.ToString.Length n = n / 10 ^ (l - SigFigs) n = Math.Round(n) n = n * 10 ^ (l - SigFigs) Return n End Function 

return new BigDecimal(value, new MathContext(significantFigures, RoundingMode.HALF_UP)).doubleValue();

Я нуждался в этом в Go, что было немного затруднено отсутствием math.Round() библиотеки стандарта math.Round() (до go1.10). Поэтому я тоже должен был взбить это. Вот мой перевод превосходного ответа Pyrolistical :

 // TODO: replace in go1.10 with math.Round() func round(x float64) float64 { return float64(int64(x + 0.5)) } // SignificantDigits rounds a float64 to digits significant digits. // Translated from Java at https://stackoverflow.com/a/1581007/1068283 func SignificantDigits(x float64, digits int) float64 { if x == 0 { return 0 } power := digits - int(math.Ceil(math.Log10(math.Abs(x)))) magnitude := math.Pow(10, float64(power)) shifted := round(x * magnitude) return shifted / magnitude } 
 public static double roundToSignificantDigits(double num, int n) { return Double.parseDouble(new java.util.Formatter().format("%." + (n - 1) + "e", num).toString()); } 

Этот код использует встроенную функцию форматирования, которая обращена к функции округления

Давайте будем гением компьютера.